码迷,mamicode.com
首页 > 其他好文 > 详细

【BZOJ 3451】Tyvj1953 Normal 思维题+期望概率+FFT+点分治

时间:2018-02-28 22:55:00      阅读:215      评论:0      收藏:0      [点我收藏+]

标签:dfs   lex   class   gpo   路径   algo   nbsp   一个   oid   

我感觉是很强的一道题……
即使我在刷专题,即使我知道这题是fft+点分治,我仍然做不出来……
可能是知道是fft+点分治限制了我的思路???(别做梦了,再怎样也想不出来的……)
我做这道题的话,一看就想单独算每个点的贡献,一开始想算每个点深度的期望,后来又想算每个点的点分树子树大小的期望,再后来就想利用点分治,于是就想算每个联通块的贡献,后来就想怂了……
开始说这道题的做法……
我们不算每个点的贡献,算每个点对的贡献!!!
我们想啊,显然,每个点的点分树子树大小的期望加和就是答案,然而呢,对于一个点,其点分树子树大小的期望就是其他点存在于其点分树子树内的概率加和,这样我们算出每个点对(i,j)i存在于j的子树的概率然后加和就好了!!!
然而怎么快速计算每个点对(i,j)i存在于j的子树的概率呢?我们仔细想一想什么情况下在点分树中j是i的祖先,就是在i到j的路径上的所有点中,j是第一个被删除的点,那么这个概率是多少呢?是1/(dis(i,j)+1),为什么呢?因为在i到j的路径上的所有点中,每个点是第一个被删除的概率是相等的(仔细想一想就会发现是这样的).
现在问题好办了,我们只要统计出每一种距离的点对个数就好了,这是一个很简单的点分治的问题,处理的时候要用fft优化一下.
感觉这道题里把n变成n^2的思想,或者说统计点对的思想,是很好很优秀的.
这道题给我的一个很大的启发就是——思维是主体,算法是工具.

#include <cmath>
#include <cstdio>
#include <cstring>
#include <complex>
#include <algorithm>
typedef double db;
typedef std::complex<db> cd;
const int N=30010,Inf=0x3f3f3f3f;
const db Pai=acos(-1.);
int dis[N<<1],num[N<<1],cnt,deep[N<<1],max_deep,tmp[N<<1];
int max,root,size[N];
bool vis[N];
int rev[N<<1],len;
cd A[N<<1],B[N<<1];
inline void fft(cd *C,int opt){
  register int i,j,k;cd temp;
  for(i=1;i<len;++i)if(rev[i]>i)std::swap(C[i],C[rev[i]]);
  for(k=2;k<=len;k<<=1){
    cd wn(cos(2*opt*Pai/k),sin(2*opt*Pai/k));
    for(i=0;i<len;i+=k){
      cd w(1.,0.);
      for(j=0;j<(k>>1);++j,w*=wn){
        temp=w*C[i+j+(k>>1)];
        C[i+j+(k>>1)]=(C[i+j]-temp);
        C[i+j]+=temp;
      }
    }
  }
}
inline void mul(int *a,int *b,int *c,int n){
  len=1;while(len<n)len<<=1;int i;
  for(i=1;i<len;++i)rev[i]=(rev[i>>1]>>1)|((i&1)?(len>>1):0);
  for(i=0;i<len;++i)A[i]=a[i],B[i]=b[i];
  fft(A,1),fft(B,1);
  for(i=0;i<len;++i)A[i]*=B[i];
  fft(A,-1);db inv=1./len;
  for(i=0;i<len;++i)c[i]=round(A[i].real()*inv);
}
struct V{
  int to,next;
}c[N<<1];
int head[N],t;
inline void add(int x,int y){
  c[++t].to=y,c[t].next=head[x],head[x]=t;
}
int n;
inline void dfs1(int x,int fa,int sum){
  int temp=0;size[x]=1;
  for(int i=head[x];i;i=c[i].next)
    if(c[i].to!=fa&&!vis[c[i].to]){
      dfs1(c[i].to,x,sum);
      size[x]+=size[c[i].to];
      temp=std::max(temp,size[c[i].to]);
    }
  temp=std::max(temp,sum-size[x]);
  if(temp<=max)max=temp,root=x;
}
inline void dfs2(int x,int fa,int d){
  ++deep[d],max_deep=std::max(max_deep,d);
  for(int i=head[x];i;i=c[i].next)
    if(c[i].to!=fa&&!vis[c[i].to])
      dfs2(c[i].to,x,d+1);
}
inline void dfs(int x,int sum){
  max=Inf,root=0,dfs1(x,0,sum);
  int i,j;num[0]=1,vis[root]=true;
  for(i=head[root];i;i=c[i].next)
    if(!vis[c[i].to]){
      dfs2(c[i].to,0,1);
      for(j=1;j<=max_deep;++j)
        num[j]+=deep[j],tmp[j]=deep[j],deep[j]=0;
      cnt=std::max(cnt,max_deep);
      mul(tmp,tmp,tmp,max_deep+max_deep+1);
      for(j=1;j<=(max_deep<<1);++j)
        dis[j]-=tmp[j],tmp[j]=0;
      max_deep=0;
    }
  for(i=0;i<=cnt;++i)
    tmp[i]=num[i],num[i]=0;
  mul(tmp,tmp,tmp,cnt+cnt+1);
  for(i=0;i<=(cnt<<1);++i)
    dis[i]+=tmp[i],tmp[i]=0;
  cnt=0;
  for(i=head[x=root];i;i=c[i].next)
    if(!vis[c[i].to])
      dfs(c[i].to,size[c[i].to]<size[x]?size[c[i].to]:sum-size[x]);
}
int main(){
  scanf("%d",&n);
  int i,x,y;
  for(i=1;i<n;++i){
    scanf("%d%d",&x,&y);
    ++x,++y,add(x,y),add(y,x);
  }
  dfs(1,n);
  db ans=0.;
  for(i=0;i<n;++i)
    ans+=(db)dis[i]/(db)(i+1);
  printf("%.4f\n",ans);
  return 0;
}

 

【BZOJ 3451】Tyvj1953 Normal 思维题+期望概率+FFT+点分治

标签:dfs   lex   class   gpo   路径   algo   nbsp   一个   oid   

原文地址:https://www.cnblogs.com/TSHugh/p/8485913.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!