Description
给出一棵n个点的树,每条边有边权1或-1.一条合法的路径(st,en)指路径上存在一个异于端点的点x,使得dis(st,x)=dis(x,en)=0.(dis不解释),求合法路径的数量。
n<=10^5
Solution
学习(复习)点分治
考虑经过当前重心x的合法路径,可以发现,满足这样的路径都是dis(st,x)+dis(x,en)=0且st到x的路径中有一个点k满足dis(k,x)=dis(st,x),或en到x的路径中有这样的点。
那我们可以处理出所有的d[i]表示dis(x,i),然后开一个桶来计算它可以和多少对点配对。
设bz[i]=1表示i到x的路径中有一个点k满足d[k]=d[i],那么
当bz[i]=1&d[i]=0时,i点可以和x配对。
所有d[i]=0的点都可以互相配对。
细节特别多(纯粹来练练手)
Code
#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define rep(i,a) for(int i=last[a];i;i=next[i])
#define N 100005
using namespace std;
typedef long long ll;
struct note{int v,s,x;}d[N];
bool cmp(note x,note y) {return x.s<y.s;}
int n,l,x,y,z,tot,dis[N],size[N],yes[N],no[N],p[N*2];
int t[N*2],next[N*2],v[N*3],last[N];
bool bz[N];
ll ans;
void add(int x,int y,int z) {
t[++l]=y;v[l]=z;next[l]=last[x];last[x]=l;
}
void get_size(int x,int y) {
size[x]=1;
rep(i,x) if (!bz[t[i]]&&t[i]!=y) {
get_size(t[i],x);
size[x]+=size[t[i]];
}
}
void get_heavy(int x,int y) {
bool f=0;
rep(i,x) if (!bz[t[i]]&&t[i]!=y) {
get_heavy(t[i],x);
if (size[t[i]]>tot/2) f=1;
}
if (tot-size[x]>tot/2) f=1;
if (!f) z=x;
}
void get_ans(int x,int y) {
if (!dis[x]&&p[N]) ans++;
ans+=yes[N-dis[x]];
if (p[N+dis[x]]) ans+=no[N-dis[x]];
p[N+dis[x]]++;
rep(i,x) if (t[i]!=y&&!bz[t[i]]) {
dis[t[i]]=dis[x]+v[i];
get_ans(t[i],x);
}
p[N+dis[x]]--;
}
void updata(int x,int y) {
if (!dis[x]||p[N+dis[x]]) yes[N+dis[x]]++;
else no[N+dis[x]]++;
p[N+dis[x]]++;
rep(i,x) if (t[i]!=y&&!bz[t[i]]) updata(t[i],x);
p[N+dis[x]]--;
}
void clear(int x,int y) {
yes[dis[x]+N]=no[dis[x]+N]=0;
rep(i,x) if (!bz[t[i]]&&t[i]!=y) clear(t[i],x);
}
void dfs(int x) {
get_size(x,0);tot=size[x];
get_heavy(x,0);bz[z]=1;
rep(i,z) if (!bz[t[i]]) {
dis[t[i]]=v[i];
get_ans(t[i],z);
updata(t[i],z);
}
clear(z,0);
rep(i,z) if (!bz[t[i]]) dfs(t[i]);
}
int main() {
scanf("%d",&n);
fo(i,1,n-1) {
scanf("%d%d%d",&x,&y,&z);
if (!z) z=-1;
add(x,y,z);add(y,x,z);
}
dfs(1);
printf("%lld",ans);
}