Submit: 949 Solved: 323
[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
树 点分治
tag上写着点分治,就按点分治做了,不然可能会用树上DP
之后在别的博客看到这么一句话:
点分治的题目和树规的题目很像,都是在树上乱搞,但点分治一般和路径更相关,如果用树规做会感觉冗余状态太多,内存和时间都无法承受,如果不用动态规划,直接在原树上运用这道题的方法,又无法保证时间复杂度(点分治让我们的算法对原树的形态依赖更小,可以保证时间复杂度)。 ——idy002 http://www.cnblogs.com/idy002/p/4374089.html
“阴““阳"权值分别记为1和-1,当一条路径权值和为0时,就说明平衡了
当选定一个点为根时,统计它的子树信息:用f[i][0/1]表示路径权值为i,无/有休息站的路径数目,g[i][0/1]表示该根已经统计过的子树中的(同左边)数目
为了防止计算中下标出现负数,将所有的权值统一加一个bas值(这里选择+n)
则ans+=(g[n][0]-1)*f[n][0] + g[n-j][1]*f[n+j][1]+g[n-j][0]*f[n+j][1]+g[n-j][1]*f[n+j][0] ( mxdeep<=j<=mxdeep)
代码原本打算仿黄学长的简洁风格写,WA了之后改呀改,渐渐就变成几乎相同了
1 /*by SilverN*/ 2 #include<algorithm> 3 #include<iostream> 4 #include<cstring> 5 #include<cstdio> 6 #include<cmath> 7 #include<vector> 8 #define LL long long 9 using namespace std; 10 const int mxn=200010; 11 int read(){ 12 int x=0,f=1;char ch=getchar(); 13 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 14 while(ch>='0' && ch<='9'){x=x*10+ch-'0';ch=getchar();} 15 return x*f; 16 } 17 struct edge{ 18 int v,nxt,w; 19 }e[mxn<<1]; 20 int hd[mxn],mct=0; 21 void add_edge(int u,int v,int w){ 22 e[++mct].nxt=hd[u];e[mct].v=v;e[mct].w=w;hd[u]=mct;return; 23 } 24 LL ans=0; 25 LL f[mxn][2];//根节点的所有已处理子树中,路径和为[i]的终点数目 26 LL g[mxn][2];//当前子树和为[i]且[有/无休息站]的终点 27 int t[mxn]; 28 int n,root; 29 int mc[mxn],sum; 30 int dep[mxn],size[mxn],mxdeep; 31 int dis[mxn]; 32 bool vis[mxn]; 33 void DFS_sz(int u,int fa){ 34 size[u]=1; 35 mc[u]=0; 36 for(int i=hd[u];i;i=e[i].nxt){ 37 int v=e[i].v; 38 if(v==fa || vis[v])continue; 39 DFS_sz(v,u); 40 size[u]+=size[v]; 41 if(size[v]>mc[u])mc[u]=size[v]; 42 } 43 mc[u]=max(mc[u],sum-size[u]); 44 if(mc[u]<mc[root])root=u;//更新重心 45 return; 46 } 47 void calc(int u,int fa){ 48 mxdeep=max(mxdeep,dep[u]); 49 if(t[dis[u]])f[dis[u]][1]++; 50 else f[dis[u]][0]++; 51 ++t[dis[u]]; 52 for(int i=hd[u];i;i=e[i].nxt){ 53 int v=e[i].v; 54 if(vis[v] || v==fa)continue; 55 dep[v]=dep[u]+1; 56 dis[v]=dis[u]+e[i].w; 57 calc(v,u); 58 } 59 --t[dis[u]]; 60 return; 61 } 62 void DFS(int x){ 63 vis[x]=1;g[n][0]=1; 64 int mx=0; 65 for(int i=hd[x];i;i=e[i].nxt){//统计以x为根的子树答案 66 int v=e[i].v; 67 if(vis[v])continue; 68 dep[v]=1;dis[v]=n+e[i].w;//dis统一加n,防止出现负数 69 mxdeep=1; 70 calc(v,0); 71 mx=max(mx,mxdeep); 72 ans+=(g[n][0]-1)*f[n][0];//以x结点为休息站而平衡的方案数(要减去结点x本身 ) 73 for(int j=-mxdeep;j<=mxdeep;j++){ 74 ans+=g[n-j][1]*f[n+j][1]+g[n-j][0]*f[n+j][1]+g[n-j][1]*f[n+j][0]; 75 } 76 int ed=n+mxdeep; 77 for(int j=n-mxdeep;j<=ed;j++){ 78 g[j][0]+=f[j][0]; 79 g[j][1]+=f[j][1]; 80 f[j][0]=f[j][1]=0; 81 } 82 } 83 int ed=n+mx; 84 for(int j=n-mx;j<=ed;j++){//初始化 85 g[j][0]=g[j][1]=0; 86 } 87 for(int i=hd[x];i;i=e[i].nxt){ 88 int v=e[i].v; 89 if(!vis[v]){ 90 root=0; 91 sum=size[v]; 92 DFS_sz(v,0); 93 DFS(root); 94 } 95 } 96 return; 97 } 98 int main(){ 99 int i,j; 100 int u,v,w; 101 n=read(); 102 for(i=1;i<n;i++){ 103 u=read();v=read();w=read();if(!w)w=-1; 104 add_edge(u,v,w); 105 add_edge(v,u,w); 106 } 107 sum=mc[0]=n; 108 DFS_sz(1,0);//统计子树尺寸 109 DFS(root); 110 printf("%lld ",ans); 111 return 0; 112 }