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\)的倍数的节点个数

轻儿子这边还好说,我们直接暴力调和级数处理一下即可,但是这样的信息从中儿子哪里却非常不好继承

考虑一下根号分治

如果这个\(i>\sqrt{n}\),也就是直接暴力从重儿子那里暴力复杂度是\(\frac{n}{i}<\sqrt{n}\),这样我们自然可以直接利用长剖时维护的数组得到

但是\(i\leq \sqrt{n}\)时,考虑维护一个数组可以快速得到这样的信息,但是这样的数组从重儿子那里没办法继承,看起来长剖好像行不太通

这个时候就需要用\(\rm dsu\)的思想了

考虑维护一个\(g[i][j]\)表示一个点子树内部点深度对\(i\)取模余数为\(j\)的点的个数,这样我们就没有必要考虑如何继承,像重儿子那样不清空直接拿来用就好了

利用这个数组我们很方便查询子树内部到当前节点距离为\(i\)或\(i\)的倍数的点的个数,只需要利用当前点的深度搞一搞就可以了

我们暴力轻儿子的时候也可以直接把更新\(g\),更新一次复杂度是\(O(\sqrt{n})\)的

但一般来说,长剖都是先重儿子再轻儿子,但是\(\rm dsu\)却是先轻儿子再重儿子,这里为了保证\(g\)里维护的信息都来自于子树内部,我们必须得像\(\rm dsu\)一样先轻儿子之后处理重儿子

代码

#include<bits/stdc++.h>
#define re register
#define LL long long
#define min(a,b) ((a)<(b)?(a):(b))
inline int read() {
    char c=getchar();int x=0;while(c<'0'||c>'9') c=getchar();
    while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-48,c=getchar();return x;
}
const int maxn=2e5+5;
struct E{int v,nxt;}e[maxn];
int n,num,__,B;
int head[maxn],son[maxn],len[maxn],dep[maxn],f[maxn],mu[maxn],p[maxn>>1];
int h[maxn],top[maxn],dfn[maxn],g[320][320],b[maxn],c[maxn];
LL ans[maxn],Ans[maxn];
inline void add(int x,int y) {
    e[++num].v=y;e[num].nxt=head[x];head[x]=num;
}
void dfs1(int x) {
    for(re int i=head[x];i;i=e[i].nxt) {
        dep[e[i].v]=dep[x]+1;dfs1(e[i].v);
        if(len[e[i].v]>len[son[x]]) son[x]=e[i].v;
    }
    len[x]=len[son[x]]+1;
}
void dfs2(int x,int topf) {
    top[x]=topf,dfn[x]=++__;
    if(!son[x]) return;
    dfs2(son[x],topf);
    for(re int i=head[x];i;i=e[i].nxt)
    if(son[x]!=e[i].v) dfs2(e[i].v,e[i].v);
}
inline void ins(int x,int v) {
    for(re int i=1;i<=B;++i) g[i][x%i]+=v;
}
inline int calc(int x,int k) {
    if(k<=B) return g[k][dep[x]%k];
    int tot=0;
    for(re int i=dfn[x]+k;i<=dfn[x]+len[x]-1;i+=k) tot+=h[i];
    return tot;
}
void dfs(int x) {
    for(re int i=head[x];i;i=e[i].nxt)
    if(son[x]!=e[i].v) dfs(e[i].v);
    if(son[x]) dfs(son[x]);
    for(re int i=head[x];i;i=e[i].nxt) {
        if(son[x]==e[i].v) continue;
        int y=e[i].v;
        for(re int j=1;j<=len[y];++j)
            b[j]=h[dfn[y]+j-1];
        for(re int j=1;j<=len[y];++j)
            for(re int k=j;k<=len[y];k+=j) c[j]+=b[k];
        for(re int j=1;j<=len[y];++j)
            ans[j]+=1ll*c[j]*calc(x,j);
        for(re int j=1;j<=len[y];++j) c[j]=b[j]=0;
        for(re int j=1;j<=len[y];++j)
            ins(j+dep[y]-1,h[dfn[y]+j-1]),h[dfn[x]+j]+=h[dfn[y]+j-1];
    }
    ins(dep[x],1);h[dfn[x]]++;
    if(x==top[x]) {
        for(re int i=1;i<=B;++i)
            for(re int j=dep[x];j<=dep[x]+len[x]-1;++j)
                g[i][j%i]=0;
    }
}
int main() {
    n=read();
    for(re int x,i=2;i<=n;++i) x=read(),add(x,i);
    dep[1]=1,dfs1(1),dfs2(1,1);f[1]=mu[1]=1;
    B=std::ceil(std::sqrt(n));B=min(B,310);dfs(1);
    for(re int i=2;i<=n;i++) {
        if(!f[i]) p[++p[0]]=i,mu[i]=-1;
        for(re int j=1;j<=p[0]&&p[j]*i<=n;++j) {
            f[p[j]*i]=1;if(i%p[j]==0) break;
            mu[p[j]*i]=-mu[i];
        }
    }
    for(re int i=1;i<=n;i++)
        for(re int j=i;j<=n;j+=i)
            Ans[i]+=1ll*mu[j/i]*ans[j];
    for(re int i=2;i<=n;++i)
        c[1]++,c[dep[i]]--;
    for(re int i=1;i<=n;i++) c[i]+=c[i-1];
    for(re int i=1;i<n;i++) printf("%lld\n",Ans[i]+c[i]);
    return 0;
}

原文地址:https://www.cnblogs.com/asuldb/p/11487538.html

时间: 2024-08-29 23:25:15

uoj33 【UR #2】树上GCD的相关文章

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

[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比较小的时候常数较小.另外递归

【UR #2】树上GCD

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

【UOJ】树上gcd

点分治 这道题还有很多种其它写法,什么长链剖分啦,启发式合并啦等等. 首先,我们可以把点对\((u,v)\)分成两类: 1.u到v的路径是一条链 2.u到v的路径不是一条链(废话) 对于第一类,显然\(f(u,v)\)就是链的长度,可以单独统计 对于第二类,就要在点分治上搞了 我们可以先计算出为d的倍数的点对数,最后容斥一下即可 在点分治中,我们取出当前子树的重心root,统计路径经过root的点对,那么又可以分成两类: A.u和v都在root的子树内 B.u和v一个在root的子树内,另一个不

开坑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#370【UR #17】滑稽树上滑稽果

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

(树上莫队)HDU - 5799 This world need more Zhu

题意: 两种询问: 1.询问以u为根的子树中出现的a次的数的和与出现b次的数的和的gcd. 2.询问u到v的树链中出现的a次的数的和与出现b次的数的和的gcd. 有点绕.. 分析: 因为自己不会树上莫队,所以学习了一波. 但是对于子树我还是有所经验,可以转成dfs序来做,之前有做过类似的题,比如这题. 然而对于树链有点懵逼,虽然我觉得也能用dfs序做,不过看大佬们的dfs序做的树链查询,也有点懵,感觉写起来很麻烦. 貌似是修改了dfs序,回溯的时候不再只是和进入时相同的序,而是独立的序. 还是感

[csu/coj 1079]树上路径查询 LCA

题意:询问树上从u到v的路径是否经过k 思路:把树dfs转化为有根树后,对于u,v的路径而言,设p为u,v的最近公共祖先,u到v的路径必定是可以看成两条路径的组合,u->p,v->p,这样一来便可以将判断条件转化为(LCA(u,k)=k  || LCA(v,k)=k) && LCA(k,p)=p.由于这个LCA询问里面需要用到中间结果p,所以这种方法用tarjan离线不行,只能用dfs+RMQ. 1 #pragma comment(linker, "/STACK:10

BC#40D GCD值统计

这题有点恶心,好多东西堆一起.刚开始看出来实质就是SG,于是很开心的敲了,然后发现统计GCD很烦,推了半天以为推出来了,写完后到最后一步,只想到暴力遍历.提交后TLE,倒回来想树上任意路径权值和的问题,这不是裸的树分治么,以前一眼能看出来的.只是最后再加个树分治略恶心.第二天早上突然发觉,统计GCD值的地方,实际上是n^2.5的,脑残了,复杂度都算不准了.问题实质就是: ∑(n,i=1)∑(m,j=1)gcd(i,j)=∑(n,i=1)∑(m,j=1)∑(d|i&d|j)?(d)=∑(min(n