【UOJ】树上gcd

点分治

这道题还有很多种其它写法,什么长链剖分啦,启发式合并啦等等。

首先,我们可以把点对\((u,v)\)分成两类:
1.u到v的路径是一条链
2.u到v的路径不是一条链(废话)
对于第一类,显然\(f(u,v)\)就是链的长度,可以单独统计

对于第二类,就要在点分治上搞了
我们可以先计算出为d的倍数的点对数,最后容斥一下即可
在点分治中,我们取出当前子树的重心root,统计路径经过root的点对,那么又可以分成两类:
A.u和v都在root的子树内
B.u和v一个在root的子树内,另一个不在

对于A类:
\(num[x]\)表示以root的儿子为根的子树内深度(相对于root的深度,下同)为x的点个数
\(Tnum[x]\)表示以root为根的子树内深度为x的点个数
\(sum[x]\)表示以root的儿子为根的子树内深度为x的倍数的点个数
\(Tsum[x]\)表示以root为根的子树内深度为x的倍数的点个数
那么我们暴力遍历root的儿子:

void DFS_Deep(int x,int fa,int dep,int &max_dep){//max_dep表示当前子树的最大深度,方便更新和清空
    max_dep=max(max_dep,dep);
    num[dep]++;
    for(int i=0;i<G[x].size();i++){
        int t=G[x][i];
        if(vis[t]||t==fa)continue;
        DFS_Deep(t,x,dep+1,max_dep);
    }
}

然后我们求出\(sum[x]\),并把\(num[x]\)和\(sum[x]\)加到\(Tnum[x]\)和\(Tsum[x]\),同时更新答案。
这部分整体代码:

void Update_Ans1(int x,int fa){
    for(int i=0;i<G[x].size();i++){
        int t=G[x][i];
        if(vis[t]||t==fa)continue;
        int tmp=0;//tmp表示以当前儿子为根的子树的最大深度
        DFS_Deep(t,x,1,tmp);//遍历
        Maxd=max(Maxd,tmp);//更新总的最大深度
        for(int j=1;j<=tmp;j++)for(int k=j;k<=tmp;k+=j)sum[j]+=num[k];//求出sum
        for(int j=1;j<=Maxd;j++)ans1[j]+=1LL*sum[j]*Tsum[j];//更新答案
        for(int j=1;j<=tmp;j++)Tsum[j]+=sum[j],Tnum[j]+=num[j];//加进去
        for(int j=1;j<=tmp;j++)sum[j]=num[j]=0;//清空
    }
}

对于B类:
这就有点麻烦了。。。
我们设当前子树的根节点为g,那我们遍历\(pre[root]\)到g的路径,对于每个点i,我们可以向上面一样求出以i为根的子树内\(num[x]\)和\(sum[x]\)的值:

int tmp=0;
for(int i=0;i<G[x].size();i++){//我拿x来代指i的
    int t=G[x][i];
    if(vis[t]||t==pre[x]||t==la)continue;//la表示这条路径是由la上去的,这也不能遍历下去
    DFS_Deep(t,x,1,tmp);
    Maxd=max(Maxd,tmp);
}
for(int j=1;j<=tmp;j++)for(int k=j;k<=tmp;k+=j)sum[j]+=num[k];

然后,我们枚举d,我们需要在root的子树中找深度间隔为d的点个数和,由于root到i还是有距离的,所以并不是从root点直接开始找。不过这样只有d种情况,我们可以记忆化,但是记忆化空间是开不下的,所以对于\(d\le \sqrt n\)我们就记忆化处理,而对于\(d>\sqrt n\)我们就直接找(难以口胡,实在不行就看代码吧)
这部分整体代码:

void Update_Ans2(int x,int la,int count){//count表示root到x的距离(x代指i)
    int tmp=0;
    for(int i=0;i<G[x].size();i++){
        int t=G[x][i];
        if(vis[t]||t==pre[x]||t==la)continue;
        DFS_Deep(t,x,1,tmp);
        Maxd=max(Maxd,tmp);
    }
    for(int j=1;j<=tmp;j++)for(int k=j;k<=tmp;k+=j)sum[j]+=num[k];
    int limit=min(tmp,Up);//Up表示sqrt(n)
    for(int j=1;j<=limit;j++){//小于sqrt(n)记忆化
        if(dp[j][count%j]==-1){//dp用来记忆化
            dp[j][count%j]=0;
            for(int k=(j-count%j)%j;k<=Maxd;k+=j)dp[j][count%j]+=Tnum[k];
        }
        ans1[j]+=1LL*dp[j][count%j]*sum[j];//更新答案
    }
    for(int j=limit+1;j<=tmp;j++){//大于sqrt(n)直接枚举
        ll res=0;
        for(int k=(j-count%j)%j;k<=Maxd;k+=j)res+=Tnum[k];
        ans1[j]+=1LL*res*sum[j];//更新答案
    }
    for(int i=1;i<=tmp;i++)sum[i]=num[i]=0;//清空
}

全部代码:

#include<bits/stdc++.h>
#define ll long long
#define MAXN 200010
using namespace std;
int n,pre[MAXN],size[MAXN],W[MAXN],root,num[MAXN],Tnum[MAXN],sum[MAXN],Tsum[MAXN],Maxd,dp[1010][1010],Up,SIZE,deep[MAXN];
ll ans1[MAXN],ans2[MAXN];
vector<int> G[MAXN];
bool vis[MAXN];
void Getroot(int x,int fa){
    size[x]=1,W[x]=0;
    for(int i=0;i<G[x].size();i++){
        int t=G[x][i];
        if(t==fa||vis[t])continue;
        Getroot(t,x);
        size[x]+=size[t];
        W[x]=max(W[x],size[t]);
    }
    W[x]=max(W[x],SIZE-size[x]);
    if(W[x]<W[root])root=x;
}
void DFS_Deep(int x,int fa,int dep,int &max_dep){
    max_dep=max(max_dep,dep);
    num[dep]++;
    for(int i=0;i<G[x].size();i++){
        int t=G[x][i];
        if(vis[t]||t==fa)continue;
        DFS_Deep(t,x,dep+1,max_dep);
    }
}
void Update_Ans1(int x,int fa){
    for(int i=0;i<G[x].size();i++){
        int t=G[x][i];
        if(vis[t]||t==fa)continue;
        int tmp=0;
        DFS_Deep(t,x,1,tmp);
        Maxd=max(Maxd,tmp);
        for(int j=1;j<=tmp;j++)for(int k=j;k<=tmp;k+=j)sum[j]+=num[k];
        for(int j=1;j<=Maxd;j++)ans1[j]+=1LL*sum[j]*Tsum[j];
        for(int j=1;j<=tmp;j++)Tsum[j]+=sum[j],Tnum[j]+=num[j];
        for(int j=1;j<=tmp;j++)sum[j]=num[j]=0;
    }
}
void Update_Ans2(int x,int la,int count){
    int tmp=0;
    for(int i=0;i<G[x].size();i++){
        int t=G[x][i];
        if(vis[t]||t==pre[x]||t==la)continue;
        DFS_Deep(t,x,1,tmp);
        Maxd=max(Maxd,tmp);
    }
    for(int j=1;j<=tmp;j++)for(int k=j;k<=tmp;k+=j)sum[j]+=num[k];
    int limit=min(tmp,Up);
    for(int j=1;j<=limit;j++){
        if(dp[j][count%j]==-1){
            dp[j][count%j]=0;
            for(int k=(j-count%j)%j;k<=Maxd;k+=j)dp[j][count%j]+=Tnum[k];
        }
        ans1[j]+=1LL*dp[j][count%j]*sum[j];
    }
    for(int j=limit+1;j<=tmp;j++){
        ll res=0;
        for(int k=(j-count%j)%j;k<=Maxd;k+=j)res+=Tnum[k];
        ans1[j]+=1LL*res*sum[j];
    }
    for(int i=1;i<=tmp;i++)sum[i]=num[i]=0;
}
void DFS_Point(int x){
    Maxd=0;
    vis[x]=true;
    Update_Ans1(x,pre[x]);
    Tnum[0]=1;
    int count=0;
    for(int i=x;;i=pre[i]){
        if(vis[pre[i]]||pre[i]==0)break;
        count++;
        Update_Ans2(pre[i],i,count);
    }
    for(int i=0;i<=Maxd;i++)Tnum[i]=Tsum[i]=0;
    int limit=min(Maxd,Up);
     for(int i=1;i<=limit;i++){
         for(int j=0;j<=i-1;j++)dp[i][j]=-1;
     }
    for(int i=0;i<G[x].size();i++){
        int t=G[x][i];
        if(vis[t])continue;
        root=0,SIZE=size[t];
        Getroot(t,0);
        DFS_Point(root);
    }
}
int main(){
    W[0]=2e9+7;
    memset(dp,-1,sizeof(dp));
    scanf("%d",&n);
    SIZE=n;
    Up=sqrt(n);
    for(int i=2;i<=n;i++)scanf("%d",&pre[i]),G[pre[i]].push_back(i),G[i].push_back(pre[i]);
    Getroot(1,0);
    DFS_Point(root);
    //统计路径是一条链的点对
    for(int i=1;i<=n;i++)deep[i]=deep[pre[i]]+1,++ans2[deep[i]-1];
    for(int i=n-1;i>=1;i--)ans2[i]+=ans2[i+1];
    for(int i=n-1;i>=1;i--){//容斥
        for(int j=i+i;j<=n-1;j+=i)ans1[i]-=ans1[j];
    }
    for(int i=1;i<=n-1;i++)printf("%lld\n",ans1[i]+ans2[i]);
    return 0;
}

原文地址:https://www.cnblogs.com/SillyTieT/p/11329065.html

时间: 2024-10-29 20:34:43

【UOJ】树上gcd的相关文章

[UOJ]#33. 【UR #2】树上GCD

题目大意:给定一棵有根树,边长均为1,对于每一个i,求树上有多少个点对,他们到lca距离的gcd是i.(n<=200,000) 做法:先容斥,求出gcd是i的倍数的点对,考虑长链剖分后从小到大合并计算答案,小的部分先把每个深度的数量变为每个深度的倍数的数量,然后若深度>k,直接到大的里面暴力,若深度<=k,我们在大的里面维护f[i][j]表示深度mod i(1<=i<=k)为j的点数,理论上k取n^0.5时达到最小复杂度O(n^1.5),实际上k比较小的时候常数较小.另外递归

UOJ UR#2树上GCD(缓存)

#include<iostream> #include<cstdio> #include<algorithm> #include<cmath> #include<cstring> using namespace std; int read() { int x=0,f=1; char ch=getchar(); while (ch<'0' || ch>'9') {if (ch=='-') f=-1; ch=getchar();} whi

【UR #2】树上GCD

这道题是有根树点分治+烧脑的容斥+神奇的分块 因为是规定1为根,还要求LCA,所以我们不能像在无根树上那样随便浪了,必须规定父亲,并作特殊讨论 因为gcd并不好求,所以我们用容斥转化一下,求x为gcd的因数的个数,这样就可以随便统计了,个人觉得代码比题解要好懂. 又因为统计完重心的所有子树,还有重心的父亲,所以在这个分支块内沿着重心的父亲一路向上爬,这时候重心的子树到重心的父亲的距离是变的,所以我们用神奇的分块大法,分类讨论,$<=\sqrt{n}$使用数组记录答案,方便以后再用到的时候统计,$

uoj33 【UR #2】树上GCD

题目 大致是长剖+\(\rm dsu\ on\ tree\)的思想 先做一个转化,改为对于\(i\in[1,n-1]\)求出有多少个\(f(u,v)\)满足\(i|f(u,v)\),这样我们最后再做一个反演就好了 既然我们要求有多少对\(f(u,v)\)是\(i\)或\(i\)的倍数,我们需要在长剖的时候快速合并两边的信息,这个信息长得非常别致,形如到当前节点距离为\(i\)或\(i\)的倍数的节点个数 轻儿子这边还好说,我们直接暴力调和级数处理一下即可,但是这样的信息从中儿子哪里却非常不好继承

开坑UR

会做几道算几道吧…… 开个坑比较能激励自己 强迫症buff+拖延症buff rating神马的?不要在意嘛 没写的都是坑 ~~~~~萌萌哒分割线~~~~~ UR#1 1 缩进优化 2 外星人 3 跳蚤国王下江南 UR#2 1 猪猪侠再战括号序列 2 跳蚤公路 3 树上GCD UR#3 1 核聚变反应强度 2 铀仓库 3 链式反应 UR#4 1 元旦三侠的游戏 只需目测就可以发现,我们可以table[b][a] 然后除了b=1的很长之外,其它都是sqrt(n)以下的 所以可以记忆化,然后b=1时把

[UOJ #58][WC2013]糖果公园(树上带修改莫队)

Description Solution 树上带修改莫队…!VFK的题解写得很清楚啦 (我的程序为什么跑得这么慢…交的时候总有一种自己在卡测评的感觉…) #include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<cmath> #include<algorithm> #define MAXN 100005 typedef long l

【做题】uoj#370滑稽树上滑稽果——巧妙dp

一个显然的结论是最终树的形态必然是一条链.具体证明只要考虑选定树上的某一条链,然后把其他部分全部接在它后面,这样答案一定不会变劣. 那么,一开始的想法是考虑每一位的最后出现位置,但这并不容易实现.注意到最终序列是单调递减的.我们在统计答案之前,把公共位先统计掉,即始终都是1的位.这样,剩下的位的最终结果都是0.这样,我们就避免了在统计时忽略某些数.那么,我们记dp[i]表示当前的结果为i的最小费用.我们在转移时枚举下一个数字是什么.这里无需担心数字的重复放置,因为它并不能让当前的数字发生变化.那

uoj#370【UR #17】滑稽树上滑稽果

题目 低智选手果然刷不动uoj 首先考虑一下构造一棵树显然是骗你玩的,按位与这个东西越做越小,挂到链的最下面显然不会劣于挂到之前的某一个点下面,所以我们只需要求一个排列使得答案最小就好了 设\(A=\max(a_i)\),发现最优答案不可能要劣于反复对一个数取\(\rm and\)的答案,我们就有了一个\(O(nA)\)的暴力,设\(dp_i\)表示当前的\(\rm and\)和为\(i\),把这个\(i\)变成\(0\)的最小代价 但是有可能最后的\(\rm and\)和也不是\(0\),于是

【UOJ 579】树上的颜色

[题目描述]: 给出一棵有N个点的有根树,结点编号为1,2,3,……,N,根结点编号为1,编号为i的结点涂上颜色Ci.现在有M个询问,每个询问要求求出以结点u为根的子树上涂有此种颜色的结点个数不小于k的颜色个数有多少. [输入描述]: 第一行包含两个正整数N和M. 第二行包含N个正整数,C1,C2,…,CN. 接下来的N-1行每行有两个正整数x和y,表示结点x和y有边相连. 再接下来的M行每行有两个正整数u和k,表示一个询问. [输出描述]: 输出M行,每行一个非负整数,对应每个询问的答案. [