【BZOJ】3697: 采药人的路径

3697: 采药人的路径

Time Limit: 10 Sec  Memory Limit: 128 MB
Submit: 1718  Solved: 602
[Submit][Status][Discuss]

Description

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

Input

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

Output

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

Sample Input

7
1 2 0
3 1 1
2 4 0
5 2 0
6 3 1
5 7 1

Sample Output

1

HINT

 

对于100%的数据,N ≤ 100,000。

 

Source

 
[Submit][Status][Discuss]


HOME Back

 

点分治经典题。但是细节好多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;
}

 

posted @ 2018-08-21 18:18  Wans_ovo  阅读(273)  评论(2编辑  收藏  举报