bzoj3697(点分治)

题解:

这题稍微有点绕

如果我们仅仅只要查询一段路径的上$0/1$路径段的数量相等的话 直接做点分就行了

但是现在需要存在一个中间转折点 要这个点到两段的$0/1$路径的数量段同样相等 所以我们考虑每个点到重心节点的$0/1$数量段的差值

我们根据节点之前的祖先节点是否出现和其一样差值的节点将其分成两类

然后分类讨论下

若当前这点属于前面有相同祖先节点具有相同差值的情况 那么他能和其他不同子树上任意一类节点产生贡献

若属于后者 那么他只能和其他不同子树中的第一类节点产生贡献

然后注意下细节就$OK$了

时间复杂度 $O(nlogn)$

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
#include <stack>
#include <queue>
#include <cmath>
#include <set>
#include <map>
#define mp make_pair
#define pb push_back
#define pii pair<int,int>
#define link(x) for(edge *j=h[x];j;j=j->next)
#define inc(i,l,r) for(int i=l;i<=r;i++)
#define dec(i,r,l) for(int i=r;i>=l;i--)
const int MAXN=3e5+10;
const double eps=1e-8;
#define ll long long
using namespace std;
const int inf=1e9;
struct edge{int t,v;edge*next;}e[MAXN<<1],*h[MAXN],*o=e;
void add(int x,int y,int vul){o->t=y;o->v=vul;o->next=h[x];h[x]=o++;}
ll read(){
ll x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
return x*f;
}
int n,key,rt,base;
int sz[MAXN],maxx[MAXN];
int dp1[MAXN<<1],dp2[MAXN<<1],dis[MAXN],Vis[MAXN<<2];
bool vis[MAXN];
ll ans;

void get_root(int x,int pre){
sz[x]=1;maxx[x]=0;
link(x){
if(j->t==pre||vis[j->t])continue;
get_root(j->t,x);
sz[x]+=sz[j->t];maxx[x]=max(maxx[x],sz[j->t]);
}
maxx[x]=max(maxx[x],base-sz[x]);
if(key>maxx[x])key=maxx[x],rt=x;
}

int tot,St[MAXN],tot1;
pii st[MAXN];

void dfs(int x,int pre){
St[++tot1]=dis[x];
//cout<<x<<":: "<<pre<<" "<<dis[x]<<endl;
if(Vis[dis[x]+n])ans+=dp1[n-dis[x]],ans+=dp2[n-dis[x]],st[++tot]=mp(dis[x],1);
else ans+=dp2[n-dis[x]],st[++tot]=mp(dis[x],2);
if(!dis[x]&&Vis[n])ans++;
if(!dis[x]&&!Vis[n])ans+=dp1[n];
Vis[dis[x]+n]++;
// cout<<x<<"::: "<<dis[x]<<" "<<ans<<endl;
link(x){
if(j->t==pre||vis[j->t])continue;
dis[j->t]=dis[x]+(j->v==0?1:-1);
dfs(j->t,x);
}
Vis[dis[x]+n]--;
}

void solve(int x){
vis[x]=1;tot1=0;
link(x){
if(vis[j->t])continue;
tot=0;dis[j->t]=(j->v==0?1:-1);dfs(j->t,x);
inc(i,1,tot)if(st[i].second==1)dp2[n+st[i].first]++;else dp1[n+st[i].first]++;
}
inc(i,1,tot1)dp1[n+St[i]]=dp2[n+St[i]]=0;
link(x){
if(vis[j->t])continue;
key=inf;base=sz[j->t];get_root(j->t,0);
solve(rt);
}
}

int main(){
n=read();
int x,y,z;
ans=0;
inc(i,2,n)x=read(),y=read(),z=read(),add(x,y,z),add(y,x,z);
key=inf;base=n;get_root(1,0);
solve(rt);
printf("%lld\n",ans);
return 0;
}

题目描述

采药人的药田是一个树状结构,每条路径上都种植着同种药材。
采药人以自己对药材独到的见解,对每种药材进行了分类。大致分为两类,一种是阴性的,一种是阳性的。
采药人每天都要进行采药活动。他选择的路径是很有讲究的,他认为阴阳平衡是很重要的,所以他走的一定是两种药材数目相等的路径。采药工作是很辛苦的,所以他希望他选出的路径中有一个可以作为休息站的节点(不包括起点和终点),满足起点到休息站和休息站到终点的路径也是阴阳平衡的。他想知道他一共可以选择多少种不同的路径。

Input

第$1$行包含一个整数$N$。
接下来$N-1$行,每行包含三个整数$a_i b_i t_i$,表示这条路上药材的类型。

Output

输出符合采药人要求的路径数目。