[JZOJ3234] 阴阳

题目

题目大意

有一棵树,每条边有 0 0 0 1 1 1两种颜色。
求满足存在 ( u , v ) (u,v) (u,v)路径上的点 x x x使得 ( u , x ) (u,x) (u,x) ( x , v ) (x,v) (x,v)路径上的两种颜色出现次数相同的点对 ( u , v ) (u,v) (u,v)数量。


思考历程

在看到这题之前我就已经知道这题是点分治了……
然而看了题目后,很长一段时间搞错了题目大意……
后来终于搞懂了题目大意,于是开始想正解。
想了半天,心态崩了……点分治我在差不多一年前才打过一题啊!
很久后搜题解,粗略看一看,没有一个看得懂……
于是又开始自己刚……然后刚出来了……
结果WA了……
调了不知道多久,终于AC……


正解

正解就是点分治啦……
显然的思路是将 0 0 0颜色的边权值记为 1 1 1 1 1 1颜色的边权值记为 − 1 -1 1
如果 ( u , v ) (u,v) (u,v)满足条件,一定有点 x x x使得 ( u , x ) (u,x) (u,x) ( x , v ) (x,v) (x,v)路径上的权值和为 0 0 0
现在设重心为 r o o t root root,现在我们要求的路径必须经过 r o o t root root
求出每个点到 r o o t root root路径上的权值和 d i s x dis_x disx
显然,如果 ( u , v ) (u,v) (u,v)满足条件,一定有 d i s u + d i s v = 0 dis_u+dis_v=0 disu+disv=0
还有一个条件是存在 u u u v v v的祖先 x x x,使得 d i s x = d i s u dis_x=dis_u disx=disu d i s x = d i s v dis_x=dis_v disx=disv
然后我们就搬出几个桶……
t o t i tot_i toti表示 d i s dis dis值为 i i i的点的数量, c a n i can_i cani表示 d i s dis dis值为 i i i并且其祖先有 d i s dis dis值和它相同的点的数量。这两个都是前面计算过的 r o o t root root的所有子树的。同理,设 s u b i sub_i subi s c n i scn_i scni,意义相同,表示现在正在计算的这个子树的。
s u b i sub_i subi的计算方法显然,至于 s c n i scn_i scni,我们在 d f s dfs dfs的过程中再维护一个桶 a n c i anc_i anci,表示 d i s dis dis值为 i i i的祖先有多少个,这样就可以快速判断了。
t o t i tot_i toti c a n i can_i cani可以由后两者累加而得。
计算答案的时候,枚举 d i s dis dis值,用前面四个桶里的值来计算,具体来说,如果 ( u , v ) (u,v) (u,v)能被算入答案中,则 u u u v v v至少有一个在 c a n can can s c n scn scn中。
注意,我们还没有计算 v = r o o t v=root v=root的情况。针对这种情况,我们维护一个 c n t 0 cnt0 cnt0,在 d f s dfs dfs的时候遇到 a n c d i s i > 1 anc_{dis_i}>1 ancdisi>1时,便意味着除了 r o o t root root之外,还有一个和它 d i s dis dis值相同的祖先,那就将其加入 c n t 0 cnt0 cnt0中。答案加上 c n t 0 cnt0 cnt0即可。

所以说实际上这是一道点分治的模板题啊……


代码

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <climits>
#include <cassert>
#define N 100010
int n;
struct EDGE{
	int to,len;
	EDGE *las;
	bool ok;
} e[N*2];
int ne;
EDGE *last[N];
inline void link(int u,int v,int len){
	e[ne]={v,len,last[u],1};
	last[u]=e+ne++;
}
#define rev(ei) (e+(int((ei)-e)^1))
long long ans;
int siz[N],all;
void get_siz(int x,int fa){
	siz[x]=1;
	for (EDGE *ei=last[x];ei;ei=ei->las)
		if (ei->to!=fa && ei->ok){
			get_siz(ei->to,x);
			siz[x]+=siz[ei->to];
		}
}
int find(int x,int fa){//找重心
	bool bz=(all-siz[x]<<1<=all);
	for (EDGE *ei=last[x];ei;ei=ei->las)
		if (ei->to!=fa && ei->ok){
			int tmp=find(ei->to,x);
			if (tmp)
				return tmp;
			bz&=(siz[ei->to]<<1<=all);
		}
	if (bz)
		return x;
}
int dis[N];
int _tot[N*2],*tot=_tot+N;//为了代码方便,所以用指针来处理了……
int _sub[N*2],*sub=_sub+N;
int _can[N*2],*can=_can+N;
int _scn[N*2],*scn=_scn+N;
int cnt0;
int _anc[N*2],*anc=_anc+N;
int mn,mx,smn,smx;//记录mn,mx是为了方便清空桶。
void get_dis(int x,int fa){
	smn=min(smn,dis[x]),smx=max(smx,dis[x]);
	sub[dis[x]]++;
	if (anc[dis[x]]){
		scn[dis[x]]++;
		if (anc[dis[x]]>1 && dis[x]==0)
			cnt0++;
	}
	anc[dis[x]]++;
	for (EDGE *ei=last[x];ei;ei=ei->las)
		if (ei->to!=fa && ei->ok){
			dis[ei->to]=dis[x]+ei->len;
			get_dis(ei->to,x);
		}
	anc[dis[x]]--;
}
void divide(int x){
	get_siz(x,0);
 	all=siz[x];
	int root=find(x,0);
	dis[root]=0;
	mn=INT_MAX,mx=INT_MIN;
	anc[0]=1;
	for (EDGE *ei=last[root];ei;ei=ei->las)
		if (ei->ok){
			smn=INT_MAX,smx=INT_MIN;
			cnt0=0;
			dis[ei->to]=dis[root]+ei->len;
			get_dis(ei->to,root);
			for (int j=smn;j<=smx;++j)
				ans+=1ll*sub[j]*can[-j]+1ll*scn[j]*tot[-j]-1ll*scn[j]*can[-j];//容斥原理
			ans+=cnt0;
			for (int j=smn;j<=smx;++j){
				tot[j]+=sub[j],sub[j]=0;
				can[j]+=scn[j],scn[j]=0;
			}
			mn=min(mn,smn);
			mx=max(mx,smx);
		}
	anc[0]=0;
	for (int i=mn;i<=mx;++i)
		tot[i]=can[i]=0;
	for (EDGE *ei=last[root];ei;ei=ei->las)
		if (ei->ok){
			ei->ok=rev(ei)->ok=0;
			divide(ei->to);
		}
}
int main(){
	scanf("%d",&n);
	for (int i=1;i<n;++i){
		int u,v,type;
		scanf("%d%d%d",&u,&v,&type);
		link(u,v,type==0?1:-1);
		link(v,u,type==0?1:-1);
	}
	divide(1);
	printf("%lld\n",ans);
	return 0;
}

总结

点分治的题目还是打得太少了……

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值