bzoj3697 采药人的路径

http://www.elijahqi.win/2018/01/17/bzoj3697/
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。
对于自己这种辣鸡怎么可能自己想出来 当然是膜了icefox巨佬的题解
1、首先将每个0的权值转化为-1 那么我可以得出 我这道题求得其实是有多少对点他们的权值和为0 然后由于他们中间必须有个休息点 所以必须阴阳平衡
那么对于这棵树开始点分 针对每个重心去统计答案 怎么统计答案 有可能休息点在我的重心上 或者是在我到子树的路径的前半段或者后半段上 如果满足阴阳平衡那么必定两个端点到重心的距离互为相反数 不妨设f[i][0/1]表示针对我目前正在做的子树 根到他距离为i 0表示是不合法的路径数 1表示合法的路径数 那么根据树形dp类似的我考虑这棵子树的时候只和前面去搞 然后做完这棵再添加影响 就不会重复所以设g[i][0/1]表示的和f基本相同只不过g[]存的是前面所有做过的子树 每次得要把恰好根就是休息站的答案加入 注意dfs的时候 我那个v应该采取类似栈的操作 一开始我直接暴力改0改1 样例都过不去为什么这样搞 因为我存在很多权值相同的暴力改完就不对了 dp转移可以去我的友链巨佬的blog看一看orz

#include<cstdio>
#include<algorithm>
#define inf 0x3f3f3f3f
#define ll long long
#define N 110000
using namespace std;
inline char gc(){
    static char now[1<<16],*S,*T;
    if (T==S) {T=(S=now)+fread(now,1,1<<16,stdin);if (T==S) return EOF;}
    return *S++;
}
inline int read(){
    int x=0;char ch=gc();
    while(ch<'0'||ch>'9') ch=gc();
    while(ch<='9'&&ch>='0') x=x*10+ch-'0',ch=gc();
    return x;
}
struct node{
    int y,z,next;
}data[N<<1];
int ff[N],size[N],n,sum,num,max_deep,dis[N<<1],dep[N<<1],v[N<<1],root,h[N];bool visit[N];
ll f[N<<1][2],g[N<<1][2],ans;
inline void get_root(int x,int fa){
    ff[x]=0;size[x]=1;
    for (int i=h[x];i;i=data[i].next){
        int y=data[i].y;if (y==fa||visit[y]) continue;
        get_root(y,x);size[x]+=size[y];ff[x]=max(ff[x],size[y]);
    }ff[x]=max(ff[x],sum-size[x]);
    if (ff[root]>ff[x]) root=x;
}
inline void calc(int x,int fa){
    if (v[dis[x]+n]) ++f[dis[x]+n][1];else ++f[dis[x]+n][0];max_deep=max(max_deep,dep[x]);++v[dis[x]+n];
    for (int i=h[x];i;i=data[i].next){
        int y=data[i].y,z=data[i].z;if (visit[y]||y==fa) continue;
        dis[y]=dis[x]+z;dep[y]=dep[x]+1;calc(y,x);
    }--v[dis[x]+n];
}
inline void solve(int x){
    visit[x]=1;int mx=0,sum1=sum;g[n][0]=1;dis[x]=0;dep[x]=0;
    for (int i=h[x];i;i=data[i].next){
        int y=data[i].y,z=data[i].z;if (visit[y]) continue;
        dis[y]=dis[x]+z;dep[y]=dep[x]+1;max_deep=0;calc(y,x);mx=max(mx,max_deep);
        ans+=(ll)f[n][0]*(g[n][0]-1);
        for (int j=-max_deep;j<=max_deep;++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=-max_deep;j<=max_deep;++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=h[x];i;i=data[i].next){
        int y=data[i].y;if (visit[y]) continue;
        root=0;if (size[y]>size[x]) sum=sum1-size[x];
        else sum=size[y];get_root(y,x);solve(root); 
    }
}
int main(){
    freopen("bzoj3697.in","r",stdin);
    n=read();
    for (int i=1;i<n;++i){
        int x=read(),y=read(),z=read();if (!z) z=-1;
        data[++num].y=y;data[num].z=z;data[num].next=h[x];h[x]=num;
        data[++num].y=x;data[num].z=z;data[num].next=h[y];h[y]=num;
    }root=0;ff[0]=inf;sum=n;get_root(1,0);solve(root);
    printf("%lld",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值