【CF#715C】Digit Tree 点分治+乘法逆元

AC通道:http://codeforces.com/problemset/problem/715/C

【题目大意】

给定一个有N个点的树,问其中有多少条路径满足他们的边权连成的数对M取余为0。

其中gcd(M,10)=1。

【题解】

首先是点分治的套路,然后这题的主要难点在如何统计连通块中的答案。

对于x→y的路径,也就是x→root→y,我们可以处理出dis[x]和dis[y]分别表示x到root、root到y连成的数对mod取模的结果

那么在合并时,如果路径合法,则应满足以下条件:


即:

然后我们就可以愉快地使用dfs统计答案了。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<map>
using namespace std;
typedef long long ll;
#define FILE "read"
#define MAXN 1000010
#define INF 1000000000
#define up(i,j,n) for(ll i=j;i<=n;++i)
#define dn(i,j,n) for(ll i=j;i>=n;--i)
#define cmax(a,b) a=max(a,b)
#define cmin(a,b) a=min(a,b)
namespace INIT{
	char buf[1<<15],*fs,*ft;
	inline char getc(){return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;}
	inline ll read(){
		ll x=0,f=1;  char ch=getc();
		while(!isdigit(ch))  {if(ch=='-')  f=-1;  ch=getc();}
		while(isdigit(ch))  {x=x*10+ch-'0';  ch=getc();}
		return x*f;
	}
}using namespace INIT;
struct node{ll y,next,v;}e[MAXN*2];
ll n,mod,sum,root,len,ans,Link[MAXN],dis[MAXN],inv[MAXN],p[MAXN],deep[MAXN],size[MAXN],f[MAXN],vis[MAXN];
map<ll,ll>cnt;
void insert(ll x,ll y,ll v){e[++len].next=Link[x];Link[x]=len;e[len].y=y;e[len].v=v;}
void exgcd(ll a,ll b,ll &x,ll &y){
	if(!b) {x=1;y=0;return;}
	exgcd(b,a%b,x,y);
	ll t=x;x=y;y=t-a/b*y;
}
ll Ni(ll num){
	ll x,y; exgcd(num,mod,x,y);
	return (x%mod+mod)%mod;
}
void pre(){
	p[0]=1;up(i,1,n)p[i]=p[i-1]*10%mod;
	up(i,0,n)inv[i]=Ni(p[i]);
}
void getroot(ll x,ll fa){
	f[x]=0; size[x]=1;
	for(ll i=Link[x];i;i=e[i].next){
		if(e[i].y==fa||vis[e[i].y])  continue;
		getroot(e[i].y,x);  size[x]+=size[e[i].y];
		cmax(f[x],size[e[i].y]);
	}
	cmax(f[x],sum-size[x]);
	if(f[x]<f[root])  root=x;
}
void dfs(ll x,ll fa){
	ll temp=((mod-dis[x]*inv[deep[x]])%mod+mod)%mod; cnt[temp]++;
	for(ll i=Link[x];i;i=e[i].next){
		if(vis[e[i].y]||e[i].y==fa) continue;
		dis[e[i].y]=(dis[x]*10%mod+e[i].v)%mod;
		deep[e[i].y]=deep[x]+1;
		dfs(e[i].y,x);
	}
}
ll get(ll x,ll fa){
	ll temp=cnt[dis[x]%mod];
	for(ll i=Link[x];i;i=e[i].next){
		if(vis[e[i].y]||e[i].y==fa) continue;
		deep[e[i].y]=deep[x]+1;
		dis[e[i].y]=(dis[x]+e[i].v*p[deep[x]]%mod)%mod;
		temp+=get(e[i].y,x);
	}
	return temp;
}
ll cal(ll x,ll v,ll depth){
	dis[x]=v%mod;  deep[x]=depth;
	dfs(x,0);  cnt[0]--;
	return get(x,0);
}
void solve(ll x){
	vis[x]=1; cnt.clear(); ans+=cal(x,0,0);
	for(ll i=Link[x];i;i=e[i].next){
		if(vis[e[i].y])  continue;
		cnt.clear();
		ans-=cal(e[i].y,e[i].v,1);
		root=0; sum=size[e[i].y]; getroot(e[i].y,0);
		solve(root);
	}
}
int main(){
	freopen(FILE".in","r",stdin);
	freopen(FILE".out","w",stdout);
	n=sum=read();  mod=read();  f[0]=INF;  pre();
	up(i,1,n-1){ll x=read()+1,y=read()+1,v=read()%mod;insert(x,y,v);insert(y,x,v);}
	getroot(1,0);  solve(root);
	printf("%lld\n",ans);
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值