1 条题解
-
0
自动搬运
来自洛谷,原作者为

jiangyougogogo
**搬运于
2025-08-24 21:22:44,当前版本为作者最后更新于2016-10-30 08:39:57,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
难得成为此题第一个吃螃蟹的蒟蒻……
这道题的思维难度不是太大,但实现难度稍大,有一定综合性。
#基本递推
首先,我们记F[a][b]表示a和b的基因相似度。
不难发现,F[a][b]=F[b][a],自身没有祖先的那些点之间F[a][b]=0,自身和自身F[a][a]=1。
归纳证明可得到F[a][b]=(F[f[a][0]][b]+F[f[a][1]][b])/2,其中f[a][0],f[a][1]是a的父母,且a的辈分比b低或相同。如果不是很容易理解可以手动计算一下样例。
所以我们已经找到了递推公式。实现的时候可以先确定所有点的辈分(用类似拓扑的方法),然后记忆化搜索(直接递推比较难确定先后顺序,不提倡)。
#高精度小数
到这里我们已经明确了答案的计算方式,但是此题有一个很大的坑点——高精度小数。
如果要手写浮点数(科学计数法)或高精分数肯定是能实现的,可惜本蒟蒻并不会。注意到F[a][b]只能是0,1或纯小数,可以用定点数存放。
#时空分析
总时间复杂度O(n²(就是最大的m)×n(高精度的转移代价))=O(n³),空间复杂度也是O(n³)。
为什么高精度的存储转移代价是O(n)呢?其实此题只涉及加法和除2,O(n)计算转移操作是可以实现的。出一组数据:3是1,2的孩子,4是1,3的孩子,5是1,4的孩子,以此类推直到300是1,299的孩子,这样查询1和300的相似度的答案是1-2^(-298),需要298位小数才能存放,所以存储代价也是O(n)。
时间上1s多半是资辞的,但是空间上直接开300³的int好像会MLE。C++选手可以用vector来存放小数来防止MLE。也可以用short类型。
虽然写得又长又难看,但还是贴一下代码吧。相信大家都是诚实的好孩子不会抄题解。
/*luoguP1235 AC code, Copyright © JSSY(i.e. jiangyougogogo)*/ #include<cstdio> #include<cstdlib> #include<cstring> #include<cmath> #include<ctime> #include<cctype> #include<algorithm> #include<iostream> #include<string> #include<vector> #define hk 310 using namespace std; struct DB{short N[hk];};//正序存储定点小数,N[0]代表位数,N[1]是整数部分,N[2]到N[N[0]]是小数部分 DB div(DB x,int y){//高精度除法 int rem,flg=0;DB z;if(!x.N[1]&&x.N[0]==1)return x; for(memset(z.N,0,sizeof z.N),z.N[z.N[0]=1]=x.N[1]/y,rem=x.N[1]%y;(rem||!flg)||z.N[0]<x.N[0];z.N[0]++){ z.N[z.N[0]+1]=(rem*10+x.N[z.N[0]+1])/y; rem=(rem*10+x.N[z.N[0]+1])%y;if(z.N[z.N[0]+1])flg=1; }return z; }DB plu(DB x,DB y){//高精度加法 int i=max(x.N[0],y.N[0]);DB z;for(memset(z.N,0,sizeof z.N);i;i--){ z.N[i-1]+=(z.N[i]+x.N[i]+y.N[i])/10; z.N[i]=(z.N[i]+x.N[i]+y.N[i])%10; }for(z.N[0]=max(x.N[0],y.N[0]);z.N[0]>1&&!z.N[z.N[0]];z.N[0]--); return z; }void wri(DB x){//输出,先输出百分数的整数部分,再输出小数部分 int i,t=0;for(i=1;i<4;i++)t=t*10+x.N[i]; printf("%d",t);if(x.N[0]<4){puts("%");return;} for(putchar('.'),i=4;i<=x.N[0];i++)printf("%d",x.N[i]);puts("%");return; } vector<int>eg[hk];int n,k,i,j,x,y,z,f[hk][2],m,q[hk],la[hk],to[hk],hd,tl,tt; bool iss[hk],lab[hk][hk],inq[hk];DB F[hk][hk]; DB C(int x,int y){//记忆化搜索 if(lab[x][y])return F[x][y];//计算过就直接把答案拿来 if(la[x]>la[y]||!iss[y])F[x][y]=F[y][x]=div(plu(C(f[x][0],y),C(f[x][1],y)),2); else F[x][y]=F[y][x]=div(plu(C(x,f[y][0]),C(x,f[y][1])),2);//选辈分低的向祖先方向继续搜 lab[x][y]=lab[y][x]=1;return F[x][y]; }int main(){ for(scanf("%d%d",&n,&k),i=0;i<k;i++) scanf("%d%d%d",&x,&y,&z),f[x][0]=y,f[x][1]=z,iss[x]=1,eg[y].push_back(x),eg[z].push_back(x),to[x]+=2; for(i=1;i<=n;i++)if(!iss[i])q[++tl]=i,la[i]=inq[i]=1;//类似拓扑的方法处理辈分,先把没有父母(即iss[i]==0)的那些点记作第一层,计算la[]表示层数,分层 for(hd=0;hd^tl;){ for(hd++,i=eg[q[hd]].size()-1;i+1;i--)if(!(--to[tt=eg[q[hd]][i]])&&!inq[tt]) q[++tl]=tt,inq[tt]=1,la[tt]=la[q[hd]]+1; }//BFS拓扑 for(i=1;i<=n;i++)for(j=1;j<=n;j++){ if(!iss[i]&&!iss[j])F[i][j].N[1]=0,F[i][j].N[0]=1,lab[i][j]=1; if(i==j)F[i][j].N[1]=1,F[i][j].N[0]=1,lab[i][j]=1; }//赋初值,其中lab[i][j]表示当前位置有没有被计算过 for(scanf("%d",&m);m;m--){ scanf("%d%d",&x,&y); wri(C(x,y));//计算即输出 }return 0; }
- 1
信息
- ID
- 235
- 时间
- 1000ms
- 内存
- 125MiB
- 难度
- 6
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者