题解 CF715C【Digit Tree】

· · 题解

CF715C Digit Tree解题报告:

更好的阅读体验

题意

给定一颗n个点的带边权w_i的树,给定m,求树上有多少条路径从左往右连成的数为m的倍数。((x,y)(y,x)为不同的点对)

## 分析 这道题告诉我们,只要肯推式子,很多静态的树上路径计数问题树上启发式合并(dsu on tree)是可以胜任的(如果不会树上启发式合并可以练习一下[CF246E Blood Cousins Return](https://www.luogu.com.cn/problem/CF246E))。 首先不难发现树上的路径形成的数是高精度也存不下的,因此我们考虑把是$m$的倍数转化为对$m$取模,那么我们就只需要在$m$的同余系下运算了。 很容易预处理出来$dis1_x$表示$1$到$x$路径的值,$dis2_x$表示$x$到$1$路径的值,$dep_x$表示$x$的深度(根节点深度为$1$)。 那么我们也可以很方便的计算出来$x$到$y$路径上的值(设$z=\text{lca}(x,y)$): $$val(x,y)=\frac{dis2_x-dis2_z}{10^{dep_z}}\times 10^{dep_y-dep_z}+dis1_y-dis1_z\times 10^{dep_y-dep_z}$$ 题目要求的就是$val(x,y)$的$(x,y)$对数,看到静态树上路径计数,我们可以考虑用树上启发式合并进行计算: > 树上启发式合并具体步骤:对于节点$x$,先计算所有轻儿子的贡献(统计贡献用全局的桶),每统计完一个轻儿子暴力消除贡献,之后统计重儿子的贡献(不清空桶),最后再把轻儿子的贡献加上并计算路径的$\text{lca}$为$x$的贡献。 如果删除和添加节点贡献复杂度为$O(1)$,那么树上启发式合并的复杂度为$O(n\log n)$。 不难发现添加节点的时候仅知道路径的一端,所以我们可以对上面的$val(x,y)\equiv 0\pmod m$的式子进行变形(上面的式子代表$x$是左端点,下面的式子代表$x$是右端点): $$dis2_x\equiv dis2_z-\frac{dis1_y\times 10^{2dep_z}}{10^{dep_y}}+dis1_z\times 10^{dep_z}\pmod m$$ $$\frac{dis1_y}{10^{dep_y}}\equiv \frac{dis1_z}{ 10^{dep_z}}-\frac{dis2_x-dis2_z}{10^{2dep_z}}\pmod m$$ 那么我们就直接用$\text{map}$存一下左边的值,然后讨论一下就好了(注意这里$\text{map}$不能存右边的值的原因是$dep_z$在重儿子遍历完的时候改变)。 ## 代码 两个要注意的地方:节点编号从$0$开始;$m$不是质数,需要用扩展欧几里得求逆元。 dsu要注意实现的细节 ``` #include<stdio.h> #include<map> using namespace std; const int maxn=100005,maxm=maxn<<1; int n,m,e,dfns,inv10; long long ans; int start[maxn],to[maxm],then[maxm],worth[maxm],size[maxn],iptson[maxn],L[maxn],R[maxn],id[maxn],pow10[maxn<<1],powinv[maxn<<1],dep[maxn],dis1[maxn],dis2[maxn];//dis1[x]:1->x dis2[x]:x->1 map<int,int>mapx,mapy; inline void add(int x,int y,int z){ then[++e]=start[x],start[x]=e,to[e]=y,worth[e]=z; } int exgcd(int a,int b,int &x,int &y){ if(b==0){ x=1,y=0; return a; } int res=exgcd(b,a%b,y,x); y-=(a/b)*x; return res; } int getinv(int a,int mod){ int x,y; exgcd(a,mod,x,y); return (x%mod+mod)%mod; } void dfs1(int x,int last){ L[x]=++dfns,id[dfns]=x,size[x]=1,iptson[x]=-1,dep[x]=dep[last]+1; for(int i=start[x];i;i=then[i]){ int y=to[i]; if(y==last) continue; dis1[y]=(10ll*dis1[x]%m+worth[i])%m,dis2[y]=(dis2[x]+1ll*pow10[dep[x]]*worth[i]%m)%m; dfs1(y,x),size[x]+=size[y]; if(iptson[x]==-1||size[iptson[x]]<size[y]) iptson[x]=y; } R[x]=dfns; } inline void calc(int x,int z){ ans+=mapx[(dis2[z]-1ll*dis1[x]*pow10[dep[z]*2]%m*powinv[dep[x]]%m+1ll*dis1[z]*pow10[dep[z]]%m+m)%m]; ans+=mapy[(1ll*dis1[z]*powinv[dep[z]]%m-1ll*(dis2[x]-dis2[z]+m)%m*powinv[dep[z]*2]%m+m)%m]; } void dfs2(int x,int last){ for(int i=start[x];i;i=then[i]){ int y=to[i]; if(y==last||y==iptson[x]) continue; dfs2(y,x),mapx.clear(),mapy.clear(); } if(iptson[x]!=-1) dfs2(iptson[x],x); calc(x,x); mapx[dis2[x]]++,mapy[1ll*dis1[x]*powinv[dep[x]]%m]++; for(int i=start[x];i;i=then[i]){ int y=to[i]; if(y==last||y==iptson[x]) continue; for(int j=L[y];j<=R[y];j++) calc(id[j],x); for(int j=L[y];j<=R[y];j++) mapx[dis2[id[j]]]++,mapy[1ll*dis1[id[j]]*powinv[dep[id[j]]]%m]++; } } int main(){ scanf("%d%d",&n,&m); if(m==1){ printf("%lld\n",1ll*n*(n-1)); return 0; } inv10=getinv(10,m),pow10[0]=powinv[0]=1; for(int i=1;i<=n*2;i++) pow10[i]=pow10[i-1]*10ll%m,powinv[i]=1ll*powinv[i-1]*inv10%m; for(int i=1;i<n;i++){ int x,y,z; scanf("%d%d%d",&x,&y,&z); x++,y++,add(x,y,z),add(y,x,z); } dfs1(1,0),dfs2(1,0); printf("%lld\n",ans); return 0; } ```