对于这个题,没有什么好用的数学性质,那么考虑统计所有长度的路径条数
路径统计问题不难想到点分治之后统计每个点的路径条数即可
假设之前子树到根的距离集合存为B,其中B[i]表示到根距离为i的有多少条
当前子树为A,那么所有路径生成路径不拿发现符合A*B的卷积形式
而且对于一个分治块,路径长度不超过结点个数,因此规模可以降低到同阶
每次对一个子树用FFT计算统计,可以写出如下代码:
#include<cstdio> #include<complex> #include<iostream> using namespace std; typedef long long LL; const int Mn=200005; const double Pi=acos(-1); struct Edge{int to,next;}w[Mn]; int n,cnt=0,h[Mn],vst[Mn],minn,tg,s[Mn],sz,prm[Mn]; LL sum[Mn],sp[Mn],sn[Mn]; complex<double>ft[Mn],fft1[Mn],fft2[Mn]; void FFT(complex<double>A[],int n,int ty){ int i,j,k,m; complex<double>t0,t1; for(i=0;i<n;i++){ for(j=0,k=i,m=1;m<n;m<<=1,j=(j<<1)|(k&1),k>>=1); if(i<j)t0=A[i],A[i]=A[j],A[j]=t0; } ft[0]=1; for(m=1;m<n;m<<=1){ t0=exp(complex<double>(0,ty*Pi/m)); for(i=1;i<m;i++)ft[i]=ft[i-1]*t0; for(k=0;k<n;k+=(m<<1)) for(i=k;i<k+m;i++){ t0=A[i];t1=A[i+m]*ft[i-k]; A[i]=t0+t1;A[i+m]=t0-t1; } } if(ty==1)return; for(t0=1.0/n,i=0;i<n;i++)A[i]*=t0; } void AddEdge(int x,int y){w[++cnt]=(Edge){y,h[x]};h[x]=cnt;} void init(){ int i,x,y; scanf("%d",&n); for(i=1;i<n;i++){ scanf("%d%d",&x,&y); AddEdge(x,y);AddEdge(y,x); } } void Size(int x,int fa){ int j,y;s[x]=1; for(j=h[x];j;j=w[j].next){ y=w[j].to; if(vst[y]||y==fa)continue; Size(y,x); s[x]+=s[y]; } } void Find(int x,int fa){ int j,y,mx=0; for(j=h[x];j;j=w[j].next){ y=w[j].to; if(vst[y]||y==fa)continue; Find(y,x); mx=max(mx,s[y]); } mx=max(mx,sz-s[x]); if(mx<minn){minn=mx;tg=x;} } int FindCen(int x){ minn=0x7fffffff/2;tg=-1; Size(x,0); sz=s[x]; Find(x,0); return tg; } void DFS(int x,int fa,int dep){ int j,y; sn[dep]++; for(j=h[x];j;j=w[j].next){ y=w[j].to; if(y==fa||vst[y])continue; DFS(y,x,dep+1); } } void Clear(LL a[],int N){for(int i=0;i<N;i++)a[i]=0;} void Calc(LL a[],LL b[],int N){ for(int i=0;i<N;i++)fft1[i]=a[i]; for(int i=0;i<N;i++)fft2[i]=b[i]; FFT(fft1,N,1);FFT(fft2,N,1); for(int i=0;i<N;i++)fft1[i]*=fft2[i]; FFT(fft1,N,-1); for(int i=0;i<N;i++)sum[i]+=(LL)(fft1[i].real()+0.3); for(int i=0;i<N;i++)a[i]+=b[i]; } void solve(int x){ int j,y,N,i; for(N=1;N<sz;N<<=1); Clear(sp,N);sp[0]=1; for(j=h[x];j;j=w[j].next){ y=w[j].to; if(vst[y])continue; Clear(sn,N); DFS(y,x,1); Calc(sp,sn,N); } } void DivOnT(int x){ int j,y,G; G=FindCen(x); solve(G); vst[G]=1; for(j=h[G];j;j=w[j].next){ y=w[j].to; if(!vst[y])DivOnT(y); } } void solve(){ int i,j;LL ans=0; for(i=2;i<=n;i++) if(!prm[i]){ ans+=sum[i]; for(j=2*i;j<=n;j+=i)prm[j]=1; } printf("%.6lf\n",ans/((double)n*(n-1)/2.0)); } int main(){ init(); DivOnT(1); solve(); return 0; }
去膜了一下其他人的代码,发现我计算过根路径条数的姿势不够优雅。。
不妨用总共的减去各个子树内部的路径,这样对于每个子树扩展大小与子树同阶,而我的方法是与父分治块同阶,复杂度不能保证
注意这样写每条路径会被统计两次,最后要除以2
#include<cstdio> #include<complex> #include<iostream> using namespace std; typedef long long LL; const int Mn=200005; const double Pi=acos(-1); struct Edge{int to,next;}w[Mn]; int n,cnt=0,h[Mn],vst[Mn],minn,tg,s[Mn],sz,prm[Mn]; LL sum[Mn],sp[Mn],sn[Mn]; complex<double>ft[Mn],fft1[Mn],fft2[Mn]; void FFT(complex<double>A[],int n,int ty){ int i,j,k,m; complex<double>t0,t1; for(i=0;i<n;i++){ for(j=0,k=i,m=1;m<n;m<<=1,j=(j<<1)|(k&1),k>>=1); if(i<j)t0=A[i],A[i]=A[j],A[j]=t0; } ft[0]=1; for(m=1;m<n;m<<=1){ t0=complex<double>(cos(Pi/m),ty*sin(Pi/m)); for(i=1;i<m;i++)ft[i]=ft[i-1]*t0; for(k=0;k<n;k+=(m<<1)) for(i=k;i<k+m;i++){ t0=A[i];t1=A[i+m]*ft[i-k]; A[i]=t0+t1;A[i+m]=t0-t1; } } if(ty==1)return; for(t0=1.0/n,i=0;i<n;i++)A[i]*=t0; } void AddEdge(int x,int y){w[++cnt]=(Edge){y,h[x]};h[x]=cnt;} void init(){ int i,x,y; scanf("%d",&n); for(i=1;i<n;i++){ scanf("%d%d",&x,&y); AddEdge(x,y);AddEdge(y,x); } } void Size(int x,int fa){ int j,y;s[x]=1; for(j=h[x];j;j=w[j].next){ y=w[j].to; if(vst[y]||y==fa)continue; Size(y,x); s[x]+=s[y]; } } void Find(int x,int fa){ int j,y,mx=0; for(j=h[x];j;j=w[j].next){ y=w[j].to; if(vst[y]||y==fa)continue; Find(y,x); mx=max(mx,s[y]); } mx=max(mx,sz-s[x]); if(mx<minn){minn=mx;tg=x;} } int FindCen(int x){ minn=0x7fffffff/2;tg=-1; Size(x,0); sz=s[x]; Find(x,0); return tg; } void DFS(int x,int fa,int dep){ int j,y; sn[dep]++; for(j=h[x];j;j=w[j].next){ y=w[j].to; if(y==fa||vst[y])continue; DFS(y,x,dep+1); } } void Clear(LL a[],int N){for(int i=0;i<N;i++)a[i]=0;} void Calc(LL a[],int N,int ty){ for(int i=0;i<N;i++)fft1[i]=a[i]; for(int i=0;i<N;i++)fft2[i]=a[i]; FFT(fft1,N,1);FFT(fft2,N,1); for(int i=0;i<N;i++)fft1[i]*=fft2[i]; FFT(fft1,N,-1); for(int i=0;i<N;i++)sum[i]+=ty*(LL)(fft1[i].real()+0.3); } void Add(LL a[],LL b[],int N){for(int i=0;i<N;i++)a[i]+=b[i];} void solve(int x){ int j,y,N,i; Size(x,0); for(N=1;N<=s[x];N<<=1); Clear(sp,N);sp[0]=1; for(j=h[x];j;j=w[j].next){ y=w[j].to; if(vst[y])continue; for(N=1;N<=2*s[y];N<<=1); Clear(sn,N);DFS(y,x,1); Calc(sn,N,-1);Add(sp,sn,N); } for(N=1;N<=s[x];N<<=1); Calc(sp,N,1);sum[0]=0; } void DivOnT(int x){ int j,y,G; G=FindCen(x); solve(G); vst[G]=1; for(j=h[G];j;j=w[j].next){ y=w[j].to; if(!vst[y])DivOnT(y); } } void solve(){ int i,j;LL ans=0; for(i=2;i<=n;i++) if(!prm[i]){ ans+=sum[i]; for(j=2*i;j<=n;j+=i)prm[j]=1; } printf("%.6lf\n",ans/2/((double)n*(n-1)/2.0)); } int main(){ init(); DivOnT(1); solve(); return 0; }