CF715C Digit Tree

传送门

题目大意

给你一棵带边权的树,求有多少点对 ( u , v ) (u,v) (u,v) 满足从 u u u v v v 路径上经过的边依次写出来构成的十进制数能够被给出的整数 M M M 整除。

Solution

从 dsu on tree 过来的,所以直接开始想 dsu 的暴力。

首先很显然我们会想到用两个数组来维护依次写出十进制数,设 u p x up_x upx 表示从 x x x 到根节点所构成数, d o w n x down_x downx 表示从根节点到 x x x 所构成的数。两者可以简单一次大法师解决。具体可以看代码,重点不是这个,所以就不展开讲。

接下来我们考虑,如果一个点对 ( s t , e d ) (st,ed) (st,ed) 是满足要求的,那么用上面的两个数组来表示就是( u u u 表示 s t st st e d ed ed L C A LCA LCA):
u p s t − u p u 1 0 d e p u × 1 0 d e p e d − d e p u + d o w n e d − d o w n u × 1 0 d e p e d − d e p u ≡ 0 ( m o d    M ) \dfrac{up_{st}-up_u}{10^{dep_u}}\times 10^{dep_{ed}-dep_u}+down_{ed}-down_u\times10^{dep_{ed}-dep_u} \equiv 0(\mod M) 10depuupstupu×10depeddepu+downeddownu×10depeddepu0(modM)

比较复杂,但是解释一下就很简单了。

我们考虑把路径分为 s t → u st\to u stu u → e d u\to ed ued,那么 s t → e d st\to ed sted 路径上构成的十进制数就是 ( s t → u ) × 1 0 d e p e d − d e p u + ( u → e d ) (st\to u)\times 10^{dep_{ed}-dep_u}+(u\to ed) (stu)×10depeddepu+(ued),这很好理解吧。

然后分别求出 s t → u st\to u stu u → e d u\to ed ued 的十进制数。这也很简单,分别是 u p s t − u p u 1 0 d e p u \dfrac{up_{st}-up_u}{10^{dep_u}} 10depuupstupu d o w n e d − d o w n u × 1 0 d e p e d − d e p u down_{ed}-down_u\times10^{dep_{ed}-dep_u} downeddownu×10depeddepu。画个图就完了。

然后考虑 dsu 的时候,我们需要对于一个点,快速查询有多少点能与之形成合法的路径,那我们分两种情况讨论:

  1. 如果当前节点作为 s t st st,那么疯狂移项得到: d o w n e d × 1 0 − d e p e d ≡ ( d o w n u − u p s t − u p u 1 0 d e p u ) × 1 0 − d e p u ( m o d    M ) down_{ed}\times 10^{-dep_{ed}} \equiv (down_u-\dfrac{up_{st}-up_u}{10^{dep_u}})\times 10^{-dep_u}(\mod M) downed×10deped(downu10depuupstupu)×10depu(modM)
    所以我们对于前面的点计数 d o w n x × 1 0 − d e p x down_{x}\times 10^{-dep_{x}} downx×10depx 加一,之后查询后面一坨东西在计数器中有多少和它相等的(当然先要模上 M M M)。

  2. 否则作为 e d ed ed,那么疯狂移项有: u p s t ≡ d o w n u × 1 0 d e p u − d o w n e d × 1 0 2 × d e p u − d e p e d + u p u ( m o d    M ) up_{st} \equiv down_u\times10^{dep_u}-down_{ed}\times 10^{2\times dep_u-dep_{ed}}+up_u(\mod M) upstdownu×10depudowned×102×depudeped+upu(modM)
    与上同理,我们塞进一个计数器里。

所以,我们用 m a p map map 作为计数器就可以了。

为了节约时间,不用每次求 L C A LCA LCA,我们直接钦定当前子树的根节点作为 L C A LCA LCA 就可以了。这样我们在处理子树的时候,先统计答案,再加上子树的信息,可以保证每次匹配的两个不在一个子树中,也就是 L C A LCA LCA 为当前根。而如果在同一子树中,在前面 dsu 的时候已经计算过了。

Code

#include<bits/stdc++.h>
#define ll long long
#define inf (1<<30)
#define INF (1ll<<60)
using namespace std;
const int MAXN=1e5+10;
ll MOD;
ll EXGCD(ll a,ll b,ll &x,ll &y){
	if(b==0){x=1;y=0;return a;}
	ll r=EXGCD(b,a%b,y,x);
	y-=a/b*x;return r;
}
ll inv(ll x){
	ll a,k;ll r=EXGCD(x,MOD,a,k);
	a=(a%MOD+MOD)%MOD;
	return a;
}
vector<pair<int,int> > e[MAXN];
ll up[MAXN],down[MAXN],tp[MAXN];
int siz[MAXN],son[MAXN],dep[MAXN];
void dfs(int x,int fa){
	siz[x]=1;son[x]=-1;
	for(auto s:e[x]){
		if(s.first==fa) continue;
		dep[s.first]=dep[x]+1;
		up[s.first]=(up[x]+tp[dep[x]]*s.second%MOD)%MOD;
		down[s.first]=(down[x]*10%MOD+s.second)%MOD;
		dfs(s.first,x);
		siz[x]+=siz[s.first];
		if(son[x]==-1||siz[son[x]]<siz[s.first])
			son[x]=s.first;
	}
}
ll p10(int x){
	if(x>=0) return tp[x];
	else return inv(tp[-x]);
}
map<long long,int> S,T;
ll ans=0;
void calc(int x,int u){
	ll st=((down[u]-(((up[x]-up[u])%MOD+MOD)%MOD)*inv(tp[dep[u]])%MOD)%MOD+MOD)%MOD*inv(tp[dep[u]])%MOD;
	ll ed=(((down[u]*tp[dep[u]]%MOD+up[u])%MOD-down[x]*p10(2*dep[u]-dep[x])%MOD)%MOD+MOD)%MOD;
	ans+=S[ed];ans+=T[st];
}
void getans(int x,int fa,int u){
	calc(x,u);
	for(auto s:e[x])
		if(s.first!=fa)
			getans(s.first,x,u);
}
void add(int x,int fa){
	S[up[x]]++;
	T[down[x]*inv(tp[dep[x]])%MOD]++;
	for(auto s:e[x])
		if(s.first!=fa)
			add(s.first,x);
}
void sub(int x,int fa){
	S[up[x]]--;
	T[down[x]*inv(tp[dep[x]])%MOD]--;
	for(auto s:e[x])
		if(s.first!=fa)
			sub(s.first,x);
}
void dsu(int x,int fa){
	for(auto s:e[x]){
		if(s.first==fa) continue;
		if(s.first==son[x]) continue;
		dsu(s.first,x);sub(s.first,x);
	}if(~son[x]) dsu(son[x],x);
	for(auto s:e[x]){
		if(s.first==fa) continue;
		if(s.first==son[x]) continue;
		getans(s.first,x,x);
		add(s.first,x);
	}calc(x,x);
	S[up[x]]++;
	T[down[x]*inv(tp[dep[x]])%MOD]++;
}
int main()
{
	int n;
	scanf("%d%lld",&n,&MOD);
	tp[0]=1;
	for(int i=1;i<=n;i++)
		tp[i]=tp[i-1]*10%MOD;
	for(int i=1,u,v,w;i<n;i++){
		scanf("%d%d%d",&u,&v,&w);u++;v++;
		e[u].push_back(make_pair(v,w));
		e[v].push_back(make_pair(u,w));
	}dfs(1,0);dsu(1,0);
	printf("%lld\n",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值