zoukankan      html  css  js  c++  java
  • uoj33 树上GCD

    题意:给你一棵树,根节点为1,每条边长度为1。定义f(u,v)=gcd(u-lca(u,v),lca(u,v)-v),求有多少个无序点对f(u,v)=i。对每个i输出答案。 n<=20W.

    标程:

     1 #include<cstdio>
     2 #include<algorithm>
     3 #include<vector>
     4 using namespace std;
     5 typedef long long ll;
     6 const int N=200005;
     7 int n,fa[N],f[N],g[N],sum[N],jp[N],dep[N],s1,s2;
     8 vector<int> vec[N];
     9 ll ans[N];
    10 const int th=256;
    11 int main()
    12 {
    13     scanf("%d",&n);
    14     for (int i=2;i<=n;i++) scanf("%d",&fa[i]),dep[i]=dep[fa[i]]+1,sum[dep[i]]++;
    15     for (int i=n;i>=1;i--) sum[i]+=sum[i+1],jp[i]=i;//u-v为链的答案 
    16     for (int d=1;d<=min(th,n);d++)
    17     {
    18       for (int i=0;i<=n;i++) f[i]=g[i]=0;
    19       for (int i=n;i>=1;i--)
    20       {
    21         g[jp[i]]+=f[i]+1; jp[i]=fa[jp[i]];
    22         ans[d]+=(ll)f[fa[i]]*g[i]; f[fa[i]]+=g[i];
    23       }    
    24     }
    25     for (int i=n;i>1;i--)
    26     {
    27         vec[i].push_back(1);//距离i有0个单位长的点有一个(i自己),相对fa[i]实现平移 
    28         if ((s1=vec[i].size())>(s2=vec[fa[i]].size()))//启发式合并 
    29           swap(vec[i],vec[fa[i]]),swap(s1,s2);
    30         if (s1>th&&s2>th)
    31         {
    32             for (int d=th+1;d<=s1;d++) 
    33             {
    34                int g1=0,g2=0;
    35                for (int j=d;j<=s1;j+=d) g1+=vec[i][s1-j];
    36                for (int j=d;j<=s2;j+=d) g2+=vec[fa[i]][s2-j];
    37                ans[d]+=(ll)g1*g2;
    38             } 
    39         }
    40         for (int j=1;j<=s1;j++) vec[fa[i]][s2-j]+=vec[i][s1-j];
    41         vec[i].clear();//清掉多余空间 
    42     }
    43     for (int i=n;i>=1;i--)//容斥 
    44       for (int j=2*i;j<=n;j+=i) ans[i]-=ans[j];
    45     for (int i=1;i<n;i++) printf("%lld
    ",ans[i]+sum[i]);
    46     return 0;
    47 }

    题解:容斥+阈值+树形dp+启发式合并:

    暴力1:枚举u,v,logn求出lca。算出深度,统计答案。O(n^2logn)

    暴力2:枚举根节点u,统计子树中深度为d的链的数量。将cnt[i]*cnt[j]统计入ans[gcd(i,j)],注意不要统计同一棵子树的答案。因为树随机,所以树高log,O(nlog^2(n))。

    暴力3:对于第4个测试点,发现是若干条链拎起来。我们考虑容斥,ans[d]表示gcd是d的倍数的点对的个数。

    如果u-v在同一条链上,那么f(u,v)=dis(u,v).在最后加上即可。反之,对于每一条链统计出长度是d的倍数的数量[h/d],求在任意两条树链上各取一条d倍数的链的方案数。用(x1^2+x2^2+...+xn^2)=x1^2+x2^2+...+xn^2+2*sigma[1<=i<j<=n]xi*xj的恒等式可以很快算出。复杂度O(n)。

    正解1:继续用容斥。树形dp求出f[i]表示i的子树中d的倍数的链有几条,辅助数组g[i]表示距i的距离%d=d-1的点数。pd[i]表示i的d-1阶祖先。

    f[i]<-g[son],ans[d]<-f[i]*g[son],g[pd[i]]<-f[i]+1。取阈值th,时间复杂度O(n*th)。

    再一。用启发式合并从底至上维护vec[size-i][d]表示i的子树中深度恰好为d的点有几个(倒序对子节点的合并前的平移比较方便)。

    合并vec[x]和vec[fa[x]]的时候就对于每个d>th,统计下深度为d的倍数的点的个数。乘法原理一下。每个点会被合并logn次,合并复杂度nlogn。计算一对父子的时间有nlogn,而深度>th的点不超过[n/th]个。时间复杂度为n^2logn/th。为了避免空间vector有n^2,每次用儿子更新完父亲后对儿子vector清零,空间复杂度O(n)。

    取th=根号n*logn最优。

    正解2:点分治,按深度点分(当然最后还是用容斥)。取出当前树重心c。

    考虑所有 u→lca(u,v)v的路径经过c产生的贡献。要么两个点都在c的子树内即lca=c,要么一个点在c的子树内。

    1.两个点都在c子树内,直接用正解1dp。深度为logn,总点数为n/2+n/4+n/8+...+1<n,复杂度为O(nlogn)。

    2.u 在 c 的子树内,v 不在。对于从c到当前子树root路径上的所有点ai,都有可能作为uvlca。对于ai旁边的子树和c的子树统计(u,v)的答案。

    枚举d,用dep/d的时间统计ai旁边子树中深度是d的倍数的点数cnt和。

    我们还需要知道 c 的子树中有多少点相对于 ai 的高度是 d 的倍数,也就是说我们要在c子树的 cnt 数组中查询下标间隔为 d 的子序列中的元素之和。

    注意到间隔为 d 时,至多只有 d 种这样的子序列,对重复查询记忆化。

    当d<根号dep时,复杂度<d*dep/d=dep,总共是O(dep^1.5);当d>根号dep时,单次对d查询复杂度为 dep/d<根号dep

    所以一层分治复杂度为O(n^1.5).根据主定理,总复杂度为O(n^1.5)

  • 相关阅读:
    一个小厂算法工程师的2021个人年终总结
    优达学城 UdaCity 纳米学位
    Eclipse 常用可视化开发插件
    Android创建文件夹和文件
    Windows Mobile 播放声音文件
    C++实现顺序栈类
    c++实现的图类
    常见的字符串操作
    常见的链表操作
    取余数法实现哈希表(包括开放定址法和链地址法解决冲突)
  • 原文地址:https://www.cnblogs.com/Scx117/p/8709018.html
Copyright © 2011-2022 走看看