BZOJ 3127:[Usaco2013 Open]Yin and Yang(树点分治)

【题目链接】 http://www.lydsy.com/JudgeOnline/problem.php?id=3127

【题目大意】

  给出一棵01边权树,求存在多少条路径,使得路径上0和1的数量相同,
  并且在路劲中能找到至少一个中断点,使得分为两段01数量相同的路径

【题解】

  我们对这棵树进行点分治,每次只考虑经过重心的路径,
  我们将路径权值和分出现一次和出现多次进行统计,如果出现一次,
  则在之前出现了多次的权值数组中查相反数,如果出现多次,
  则是一次和多次的权值数组的和,权值和为0的特殊情况则另需考虑以重心为端点的路径。
  每个子树先计算答案,然后统计入权值数组。

【代码】

#include <cstdio>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std;
const int N=200020;
vector<int> v[N],e[N];
typedef long long LL;
const int base=100010;
int n,cnt,root,sum,nowT=0;
int mark[N],T1[N],T2[N],size[N],d[N],dp[N],t[N],vis[N];
LL S1[N],S2[N],ans;
void getroot(int x,int fx){
    size[x]=1; dp[x]=0;
    for(int i=0;i<v[x].size();i++){
        int y=v[x][i];
        if(!vis[y]&&y!=fx){
            getroot(y,x);
            size[x]+=size[y];
            dp[x]=max(dp[x],size[y]);
        }
    }dp[x]=max(dp[x],sum-size[x]);
    if(dp[x]<dp[root])root=x;
}
void getdeep(int x,int fx){
    if(mark[base+d[x]]){
        mark[base+d[x]]++;
        if(!d[x])ans++;
        if(T2[base+d[x]]==root)S2[base+d[x]]++;
        else T2[base+d[x]]=root,S2[base+d[x]]=1;
    }else{
        mark[base+d[x]]++;
        if(T1[base+d[x]]==root)S1[base+d[x]]++;
        else T1[base+d[x]]=root,S1[base+d[x]]=1;
    }
    for(int i=0;i<v[x].size();i++){
        int y=v[x][i],w=e[x][i];
        if(!vis[y]&&y!=fx){
            d[y]=d[x]+w;
            getdeep(y,x);
        }
    }mark[base+d[x]]--;
}
void caldeep(int x,int fx){
    if(mark[base-d[x]]){
    	mark[base-d[x]]++;
        if(T2[base-d[x]]==root)ans+=S2[base-d[x]];
        else T2[base-d[x]]=root,S2[base-d[x]]=0;
        if(T1[base-d[x]]==root)ans+=S1[base-d[x]];
        else T1[base-d[x]]=root,S1[base-d[x]]=0;
    }else{
        mark[base-d[x]]++;
        if(T2[base-d[x]]==root)ans+=S2[base-d[x]];
        else T2[base-d[x]]=root,S2[base-d[x]]=0;
        if(T1[base-d[x]]==root){
        	if(!d[x])ans+=S1[base-d[x]];
        }else T1[base-d[x]]=root,S1[base-d[x]]=0;
    }
    for(int i=0;i<v[x].size();i++){
        int y=v[x][i],w=e[x][i];
        if(!vis[y]&&y!=fx){
            d[y]=d[x]+w;
            caldeep(y,x);
        }
    }mark[base-d[x]]--;
}
void cal(int x){
    for(int i=0;i<v[x].size();i++){
        int y=v[x][i],w=e[x][i];
        if(!vis[y]){
            d[y]=w;
            caldeep(y,x);
            getdeep(y,x);
        }
    }
}
void solve(int x){
    cal(x); vis[x]=1;
    for(int i=0;i<v[x].size();i++){
        int y=v[x][i];
        if(!vis[y]){
            root=0;sum=size[y];
            getroot(y,0);
            solve(root);
        }
    }
}
int main(){
    while(~scanf("%d",&n)){
        ans=0;
        for(int i=1;i<=n;i++)v[i].clear(),e[i].clear();
        for(int i=1;i<n;i++){
            int x,y,w;
            scanf("%d%d%d",&x,&y,&w);
            v[x].push_back(y);
            v[y].push_back(x);
            e[x].push_back(w?1:-1);
            e[y].push_back(w?1:-1);
        }memset(vis,0,sizeof(vis));
        dp[0]=sum=n;
        getroot(1,0);
        solve(root);
        printf("%lld\n",ans);
    }return 0;
}
时间: 2024-10-26 16:16:56

BZOJ 3127:[Usaco2013 Open]Yin and Yang(树点分治)的相关文章

BZOJ 4094 Usaco2013 Dec Optimal Milking 线段树

题目大意:给定n个点排成一排,每个点有一个点权,多次改变某个点的点权并将最大点独立集计入答案,输出最终的答案 开一个线段树,每个点记录四个信息: 区间左端点不选,右端点也不选的最大值 区间左端点选择,右端点不选的最大值 区间左端点不选,右端点选择的最大值 区间左端点选择,右端点也选择的最大值 然后合并时讨论一下就行了 #include <cstdio> #include <cstring> #include <iostream> #include <algorit

bzoj3127/3697 [Usaco2013 Open]Yin and Yang

传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3127 http://www.lydsy.com/JudgeOnline/problem.php?id=3697 [题解] 点分治. f[i,0/1]表示前面一坨路径和为i,是否存在休息站. 分类讨论:休息站在点分的地方,休息站在前面子树,休息站在当前子树 子树合并的时候顺便算一发贡献即可. # include <stdio.h> # include <string.h> #

BZOJ 1452: [JSOI2009]Count (二维树状数组)

Description Input Output Sample Input Sample Output 1 2 HINT 二维树状数组的简单应用,c数组的第一维坐标相当于哈希.如果是修改操作,修改前 将当前的值的个数以及祖先都减1, 修改后将个数加1. #include <iostream> #include <cstring> #include <cstdio> #include <cmath> #include <set> #include

【BZOJ 1036】【ZJOI 2008】树的统计

此题为树链剖分的裸题. 代码如下,使用常用的轻重链剖分. /************************************************************** Problem: 1036 User: Evensgn Language: C++ Result: Accepted Time:2468 ms Memory:5772 kb ****************************************************************/ #inc

【BZOJ】1146: [CTSC2008]网络管理Network(树链剖分+线段树套平衡树+二分 / dfs序+树状数组+主席树)

第一种做法(时间太感人): 这题我真的逗了,调了一下午,疯狂造数据,始终找不到错. 后来发现自己sb了,更新那里没有打id,直接套上u了.我.... 调了一下午啊!一下午的时光啊!本来说好中午A掉去学习第二种做法,噗 好吧,现在第一种做法是hld+seg+bst+二分,常数巨大,log^4级别,目前只会这种. 树剖后仍然用线段树维护dfs序区间,然后在每个区间建一颗平衡树,我用treap,(这题找最大啊,,,囧,并且要注意,这里的rank是比他大的数量,so,我们在二分时判断要判断一个范围,即要

【BZOJ】1012: [JSOI2008]最大数maxnumber(树状数组+区间最值)

http://www.lydsy.com/JudgeOnline/problem.php?id=1012 树状数组原来我只懂得sum和add的操作,今天才知道可以有求区间最值的操作,我学习了一下写了个,1a了. 区间最值其实和区间求和差不多,就是将sum数组的含义转移到max,然后通过特定的区间更新max. 在区间求和中,当我们维护max[i]的时候,要找到它前面所有的max[j]来更新,在这里因为是树状数组,所以可以降成一个log级,画图可知,max[i]需要的max只有max[i-2^0],

BZOJ 3924: [Zjoi2015]幻想乡战略游戏(动态点分治)

这种动态点分治嘛,GDKOI时听打到了,也有同学讲到了,所以印象比较深刻也就想出来了,然后就在实现方面卡了好久= = 不得不说CLJ说得真的太简单了,实现方面根本没提. 首先我们可以先用树分治构建出这棵树的分治树,也就是把这棵树的重心作为根节点然后子树为他的子树的重心这样递归下去,然后每个节点存的是其子树的信息. 对于每个节点我们保存这个子树的dv的总和已经把该节点作为点的答案值 这样对于修改能在log n的时间内解决 寻找答案的时候,我们可以发现,如果现在节点的子树dv和*2大于总节点,那么向

从零开始学建树(树的分治,树的重心)

分治算法在树的路径问题中的应用 一.树的分治算法 树的分治算法是分治思想在树型结构上的体现. 任一个具有n个节点的连通路,它的任何一棵树的树枝数为n-1 分治:除去树中的某些对象,使原树被分解成若干互不相交的部分. 分治算法分为两种:一种是点的分治,一种是边的分治 1.基于点的分治 1.选取一个点将无根树转为有根树 2.递归处理每一颗以根结点的儿子为根的子树 2.基于边的分治 1.在树中选取一条边 2. 将原有的树分成两棵不相交的树,递归处理. 可以证明在基于点的分治中,如果每次都选取树的重心,

hdu 4871 树的分治+最短路记录路径

/* 题意:给你一些节点和一些边,求最短路径树上是k个节点的最长的路径数. 解:1.求出最短路径树--spfa加记录 2.树上进行操作--树的分治,分别处理子树进行补集等运算 */ #include<stdio.h> #include<string.h> #include<stdlib.h> #include<algorithm> #include<iostream> #include<queue> #define ll __int6