zoukankan      html  css  js  c++  java
  • Tyvj 1953 Normal:多项式,点分治

    Decription:

    某天WJMZBMR学习了一个神奇的算法:树的点分治! 这个算法的核心是这样的:

    消耗时间=0

    Solve(树 a) 消耗时间 += a 的 大小

    如果 a 中 只有 1 个点,退出;否则在a中选一个点x,在a中删除点x,那么a变成了几个小一点的树,对每个小树递归调用Solve。

    我们注意到的这个算法的时间复杂度跟选择的点x是密切相关的。 如果x是树的重心,那么时间复杂度就是O(nlogn) 但是由于WJMZBMR比较傻逼,他决定随机在a中选择一个点作为x! Sevenkplus告诉他这样做的最坏复杂度是O(n^2) 但是WJMZBMR就是不信><。。。 于是Sevenkplus花了几分钟写了一个程序证明了这一点。。。你也试试看吧^^ 现在给你一颗树,你能告诉WJMZBMR他的傻逼算法需要的期望消耗时间吗?(消耗时间按在Solve里面的那个为标准)

    n<=30000

    大神题,感觉不是特别可想,但貌似还是有一点可想的。

    在各种大神引导之下想到了一半,又在他们的引导下颓题解。

    关于这道题真的要写一个点分治也是无力吐槽。

    题目中有提示啊:算法是$O(n^2)$级别的。(并不代表你的代码可以是$O(n^2)$的)

    所以可以考虑每个点对对答案的贡献。(怎么想到的???)

    考虑分治过程形成的点分树,如果两个点在点分树上是祖先关系,那么在下面的点就会被多做一次。

    否则就不会贡献时间复杂度。

    其实消耗的总时间就是所有点在点分树上的期望深度之和。

    而两个点在点分树上的关系如何考虑?

    如果两个点u,v之间的点的个数为dis(u,v)(含u和v),那么你在这么多个点里第一个选出的点就是u,v在点分树上的LCA。

    所以如果你第一次选的是u,那么点对(u,v)就会贡献1答案。选v的话点对(v,u)会贡献答案。选中的概率都是$frac{1}{dis(u,v)}$。否则均不会贡献答案。

    所以总的答案就是$sumlimits_{i=1}^{n} sumlimits_{j=1}^{n} frac{1}{dis(i,j)}$

    注意这里的dis是平时说的树上距离再+1,因为是点数而不是边数。

    所以现在的问题就转化为了求树上所有点对之间的距离,每个距离值有几种。

    经典的点分治。

    而时间限制在那里呢,所以合并两个子树的桶值的时候需要用到FFT。

    而FFT的大小是根据这个子树深度最大点的深度定的,当然不能跑满啊。

    采用的点分治策略是“这棵树内所有点对的距离-子树内重复的贡献”

    然后开一个全局的数组不断累加每种距离的出现次数,最后乘以$frac{1}{i}$即可。

     1 #include<cstdio>
     2 #include<algorithm>
     3 #include<cmath>
     4 using namespace std;
     5 #define S 33333
     6 const double pi=3.141592653589793238;
     7 struct cp{
     8     double r,i;
     9     cp operator*(cp b){return (cp){r*b.r-i*b.i,r*b.i+b.r*i};}
    10     cp operator+(cp b){return (cp){r+b.r,i+b.i};}
    11     cp operator-(cp b){return (cp){r-b.r,i-b.i};}
    12 }a[S<<2];
    13 int sz[S],mx=S,tot,fir[S],l[S<<1],to[S<<1],n,al[S],rt,mxdep,len=1,ans[S],rev[S],ec;double Ans;
    14 void FFT(int opt){
    15     for(int i=1;i<len;++i)rev[i]=rev[i>>1]>>1|(i&1?len>>1:0);
    16     for(int i=0;i<len;++i)if(rev[i]>i)swap(a[rev[i]],a[i]);
    17     for(int mid=1;mid<len;mid<<=1){
    18         cp tem=(cp){cos(pi/mid),sin(pi/mid)*opt};
    19         for(int i=0;i<len;i+=mid<<1){
    20             cp omega=(cp){1,0};
    21             for(int j=0;j<mid;++j,omega=omega*tem){
    22                 cp x=a[i+j],y=a[i+j+mid]*omega;
    23                 a[i+j]=x+y;a[i+j+mid]=x-y;
    24             }
    25         }
    26     }
    27 }
    28 void con(int a,int b){l[++ec]=fir[a];fir[a]=ec;to[ec]=b;}
    29 void get_root(int p,int fa){
    30     sz[p]=1;int tmx=0;
    31     for(int i=fir[p];i;i=l[i])if(to[i]!=fa&&!al[to[i]])get_root(to[i],p),sz[p]+=sz[to[i]],tmx=max(tmx,sz[to[i]]);
    32     tmx=max(tmx,tot-sz[p]);
    33     if(tmx<mx)rt=p,mx=tmx;
    34 }
    35 void get_deep(int p,int fa,int dep){
    36     mxdep=max(mxdep,dep);a[dep].r+=1;
    37     for(int i=fir[p];i;i=l[i])if(to[i]!=fa&&!al[to[i]])get_deep(to[i],p,dep+1);
    38 }
    39 void cal(int p,int opt){
    40     mxdep=0;get_deep(p,0,0);len=1;
    41     while(len<=mxdep<<1)len<<=1;
    42     FFT(1);
    43     for(int i=0;i<len;++i)a[i]=a[i]*a[i];
    44     FFT(-1);
    45     if(opt)for(int i=0;i<len;++i)ans[i+1]+=int(a[i].r/len+0.1),a[i].r=a[i].i=0;
    46     else for(int i=0;i<len;++i)ans[i+3]-=int(a[i].r/len+0.1),a[i].r=a[i].i=0;
    47 }
    48 void dfs(int p){
    49     al[p]=1;cal(p,1);
    50     for(int i=fir[p];i;i=l[i])if(!al[to[i]])cal(to[i],0),tot=sz[to[i]],mx=S,get_root(to[i],p),dfs(rt);
    51 }
    52 int main(){
    53     scanf("%d",&n);
    54     for(int i=1,x,y;i<n;++i)scanf("%d%d",&x,&y),con(++x,++y),con(y,x);
    55     tot=n;get_root(1,0);dfs(rt);
    56     for(int i=1;i<=n;++i)Ans+=1.0*ans[i]/i;
    57     printf("%.4lf
    ",Ans);
    58 }
    神仙题
  • 相关阅读:
    jQuery 语法
    HTML DOM Document 对象
    JavaScript
    JavaScript Cookies
    JavaScript 计时事件
    九度OJ 1352 和为S的两个数字
    九度0J 1374 所有员工年龄排序
    九度OJ 1373 整数中1出现的次数(从1到n整数中1出现的次数)
    九度OJ 1370 数组中出现次数超过一半的数字
    九度OJ 1361 翻转单词顺序
  • 原文地址:https://www.cnblogs.com/hzoi-DeepinC/p/12005636.html
Copyright © 2011-2022 走看看