「BZOJ3697」「FJ2014集训」采药人的路径

3697: 采药人的路径

Time Limit: 10 Sec  Memory Limit: 128 MB
Submit: 1723  Solved: 603
[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

 

今天做了昨天的一道点分治的题 细节真的可以说是很多了...

看到这道题 树上路径 然后就可以想到点分治 然后这道题就是根据乘法原理组合路径求答案的

然后对于阳性药他的权值是1 然后阴性的是-1 所以说一条阴阳平衡的路径路径和为0  

由题可知休息站到终点和起点的路径和为0 那么很显然整条路径也一定是合法的 

所以我们就只需要找到合法的路径就可以了 

以下题解摘自 http://hzwer.com/4526.html

路径上的休息站一定是在起点到根的路径上,或者根到终点的路径上。

如何判断一条从根出发的路径是否包含休息站?只要在dfs中记录下这条路径的和x,同时用个标志数组判断这条路径是否存在前缀和为x的节点。

这样我们枚举根节点的每个子树。用g[i][0 / 1],f[i][0 / 1]分别表示前面几个子树以及当前子树和为i的路径数目,

0和1用于区分路径上是否存在前缀和为i的节点。

那么当前子树的贡献就是$f[0][0] * g[0][0] + \sum (f [i][0] * g [-i][1] + f[i][1] * g[-i][0] + f[i][1] * g[-i][1])$

其中i的范围[-d,d],d为当前子树的深度。 所以就直接给所有值都加上n就可以了 这样子下标就不会超界

代码

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int oo = 1e7;
const int N = 1e5 + 5;
int n,head[N],tov[2 * N],nex[2 * N],val[2 * N],sum;
int tot,size[N],root,mxdeep,MX,deep[N],dis[N],t[N];
ll f[2 * N][2],g[2 * N][2],ans;
bool vis[2 * N];

void add(int u,int v,int w) {
    
    tot ++;
    tov[tot] = v;
    nex[tot] = head[u];
    val[tot] = w;
    head[u] = tot;
}

void find_root(int u,int fa) {
    
    size[u] = 1;
    for(int i = head[u];i;i = nex[i]) {
        int v = tov[i];
        if(v == fa || vis[v]) continue;
        find_root(v,u);
        size[u] += size[v];
    }
    int cmp = max(sum - size[u],size[u]);
    if(cmp < MX) {
        MX = cmp;
        root = u;
    }
}

void dfs(int u,int fa,int dd) {
    
    mxdeep = max(deep[u],mxdeep);
    dis[u] = dd;
    if(t[dis[u]]) f[dis[u]][1] ++;
    else f[dis[u]][0] ++;
    t[dis[u]] ++;
    for(int i = head[u];i;i = nex[i]) {
        int v = tov[i];
        if(vis[v] || v == fa) continue;
        deep[v] = deep[u] + 1;
        dfs(v,u,dd + val[i]);
    }
    t[dis[u]] -- ;
} 

void divide(int u) {
    
    vis[u] = true; g[n][0] = 1;
    deep[u] = 0; int mx = 0;
    for(int i = head[u];i;i = nex[i]) {
        int v = tov[i];
        if(vis[v]) continue;
        deep[v] = 1;
        mxdeep = 1;
        dfs(v,u,n + val[i]); 
        mx = max(mx,mxdeep);
        ans += (g[n][0] - 1) * f[n][0];
        for(int j = -mxdeep;j <= mxdeep;j ++) {
            ans += g[n - j][1] * f[n + j][0] + g[n - j][0] * f[n + j][1] + g[n - j][1] * f[n + j][1];
        }    
        for(int j = -mxdeep;j <= mxdeep;j ++) {
            g[n - j][0] += f[n - j][0];
            g[n - j][1] += f[n - j][1];
            f[n - j][0] = f[n - j][1] = 0;
        }
    }
    for(int j = -mx;j <= mx;j ++) {
            g[n - j][0] = g[n - j][1] = 0;
    }
    for(int i = head[u];i;i = nex[i]) {
        int v = tov[i];
        if(vis[v]) continue;
        sum = size[v]; MX = oo;
        find_root(v,u);
        divide(root);
    }
}

int main( ) {
    
    scanf("%d",& n);
    for(int i = 1;i < n;i ++) {
        int u,v,w;
        scanf("%d%d%d",& u,& v,& w);
        if(! w) w = -1;
        add(u,v,w);  add(v,u,w);
    }
    MX = oo; sum = n;
    find_root(1,0);
    divide(root);
    printf("%lld",ans);
}

 

posted @ 2018-08-21 21:06  阿澈说他也想好好学习  阅读(161)  评论(0编辑  收藏  举报