【BZOJ】3697: 采药人的路径
3697: 采药人的路径
Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 1718 Solved: 602
[Submit][Status][Discuss]
Description
采药人的药田是一个树状结构,每条路径上都种植着同种药材。
采药人以自己对药材独到的见解,对每种药材进行了分类。大致分为两类,一种是阴性的,一种是阳性的。
采药人每天都要进行采药活动。他选择的路径是很有讲究的,他认为阴阳平衡是很重要的,所以他走的一定是两种药材数目相等的路径。采药工作是很辛苦的,所以他希望他选出的路径中有一个可以作为休息站的节点(不包括起点和终点),满足起点到休息站和休息站到终点的路径也是阴阳平衡的。他想知道他一共可以选择多少种不同的路径。
Input
第1行包含一个整数N。
接下来N-1行,每行包含三个整数a_i、b_i和t_i,表示这条路上药材的类型。
Output
输出符合采药人要求的路径数目。
Sample Input
1 2 0
3 1 1
2 4 0
5 2 0
6 3 1
5 7 1
Sample Output
HINT
对于100%的数据,N ≤ 100,000。
Source
点分治经典题。但是细节好多aaa!!
在计算$u$的贡献,即经过$u$的路径方案数时,记录两个数组,$f[dis][0/1]$和$g[dis][0/1]$,$f$表示这棵子树之前所有计算过的子树所作出的贡献。$g$表示当前计算的子树作出的贡献,在$dfs$中更新$g$,用完过后清零更新$f$。$0/1$表示这条到重心的路径上是否有休息站。在$dfs$中开一个桶,记录之前经过过的路径长度。每次回溯回去的时候释放桶。如果当前的$dis$之前就被装进桶中过,就表示从那时到现在的这条路径的$dis$为0,表示这条路径上肯定有一个休息站,更新$g[dis][1]$,否则更新$g[dis][0]$。
$ans$会从所有$f[i][0]*g[-i][1]+f[i][1]*g[-i][0]+f[i][1]*g[-i][1]$转移过来,表示至少有一个休息站和两个休息站的情况。注意,总的还要加上$g[0][0]*(f[0][0]-1)$,表示当前重心为休息站时的贡献,【注意】$f$减一是必须的,因为之前所有子树中计算的$dis$为0的路径还包括了当前重心这一个点,不能计算进去。而要计算从这个点延伸出去的合法链则一定会在后面的$f[0][0]*g[dis][1]$中被计算到,因为这条链如果满足条件,在这条链中的某一个点一定是休息站。
【注意】答案要存为long long
#include<iostream> #include<cstdio> #define ll long long using namespace std; const int N = 200005; int n; int stot, tov[N*2], nex[N*2], h[N], w[N*2]; void add ( int u, int v, int s ) { tov[++stot] = v; w[stot] = s; nex[stot] = h[u]; h[u] = stot; } int siz[N], vis[N], size, asize, root; void findroot ( int u, int f ) { siz[u] = 1; int res = 0; for ( int i = h[u]; i; i = nex[i] ) { int v = tov[i]; if ( vis[v] || v == f ) continue; findroot ( v, u ); siz[u] += siz[v]; res = max ( res, siz[v] ); } res = max ( res, size - siz[u] ); if ( res < asize ) { asize = res; root = u; } } ll g[N][2], f[N][2]; int t[N], dis[N], maxdep, dep[N]; void dfs ( int u, int f ) { maxdep = max ( dep[u], maxdep ); if ( t[dis[u]] ) g[dis[u]][1] ++; else g[dis[u]][0] ++; t[dis[u]] ++; for ( int i = h[u]; i; i = nex[i] ) { int v = tov[i]; if ( v == f || vis[v] ) continue; dep[v] = dep[u] + 1; dis[v] = dis[u] + w[i]; dfs ( v, u ); } t[dis[u]] --; } ll ans; void work ( int u ) { vis[u] = 1; int mx = 0; f[n][0] = 1; for ( int i = h[u]; i; i = nex[i] ) { int v = tov[i]; if ( vis[v] ) continue; dis[v] = n + w[i]; dep[v] = 1; maxdep = 1; dfs ( v, 0 ); mx = max ( mx, maxdep ); ans += 1ll * ( f[n][0] - 1 ) * g[n][0]; for ( int j = -maxdep; j <= maxdep; j ++ ) ans += f[n+j][0] * g[n-j][1] + f[n+j][1] * g[n-j][0] + f[n+j][1] * g[n-j][1]; for ( int j = -maxdep; j <= maxdep; j ++ ) { f[n-j][0] += g[n-j][0]; g[n-j][0] = 0; f[n-j][1] += g[n-j][1]; g[n-j][1] = 0; } } for ( int i = -mx; i <= mx; i ++ ) f[n+i][0] = f[n+i][1] = 0; for ( int i = h[u]; i; i = nex[i] ) { int v = tov[i]; if ( !vis[v] ) { asize = 0x3f3f3f3f, size = siz[v]; root = 0; findroot ( v, 0 ); work ( root ); } } } int main ( ) { scanf ( "%d", &n ); for ( int i = 1; i < n; i ++ ) { int u, v, s; scanf ( "%d%d%d", &u, &v, &s ); s = s ? 1 : -1; add ( u, v, s ); add ( v, u, s ); } size = n; asize = 0x3f3f3f3f; findroot ( 1, 0 ); work ( root ); printf ( "%lld", ans ); return 0; }