【XSY2505】tree(树的直径+缩点)

\(Description\)

机房断网了!\(xj\)轻而易举地撬开了中心机房的锁,拉着\(zwl\)走了进去。他们发现中心主机爆炸了。

中心主机爆炸后分裂成了\(n\)块碎片,但碎片仍然互相连接,形成一个树的结构。每个碎片有一个状态值\(0\)\(1\)\(zwl\)找了一下规律,发现只有所有碎片的状态值相同的时候,主机才能够修复。

\(xj\)碰了碰其中一个碎片 \(x\) ,发现对于满足 \(x\)\(v\) 的路径上所有碎片的状态值与 \(x\) 的状态值相同 的那些碎片 \(v\) 状态值都取反(\(0\)\(1\)\(1\)\(0\))了!

现在他们要尝试修复这个网络,最少需要多少次触碰呢?


\(Input\)

碎片从 \(1\)\(n\) 编号。

第一行一个整数 \(n\) ,第二行 \(n\) 个数 \(0\)\(1\), 第 \(i\) 个数表示 \(i\) 号碎片的状态值。

接下来 \(n−1\) 行,每行两个数 \(x,y\) 表示 \(x\)\(y\) 碎片中有连接。


\(Output\)

一行一个数,表示最少需要的碰撞次数。


\(Sample Input\)

11
0 0 0 1 1 0 1 0 0 1 1
1 2
1 3
2 4
2 5
5 6
5 7
3 8
3 9
3 10
9 11


\(Sample Output\)

2


\(HINT\)

样例解释:首先触碰三号碎片,再触碰六号碎片,这样所有碎片的状态值都会变为\(1\) ,共触碰两次。

数据范围如下:

对于 \(20\%\) 的数据,\(n≤10\)

对于 \(100\%\) 的数据,\(n≤5×10^5\)


思路

题意:触碰这个点即为将这个点的同色联通块黑白取反,求需要触碰多少次使整棵树的颜色相同

我们发现这个题中有同色联通块,于是我们考虑先将同色联通块缩点

我们可以发现,缩点之后所形成的一棵树的相邻节点的颜色一定相反(黑白染色),那么我们考虑每次触碰一个点,都会将它取反,就会使他的颜色和上下的节点的颜色相同,再次缩点后,会发现树的直径每次最多减\(2\),于是一直这样实现,最后答案的最小值就是\(\left\lfloor\frac{d+1}{2}\right\rfloor\)

现在我们证明一下能否取到\(\left\lfloor\frac{d+1}{2}\right\rfloor\)

我们考虑一个点,它与其他点的距离最大不超过\(\left\lfloor\frac{d+1}{2}\right\rfloor\)

这个点是肯定存在的,因为满足\(\left\lfloor\frac{d+1}{2}\right\rfloor\)+\(\left\lfloor\frac{d+1}{2}\right\rfloor\)\(+1>n\)

接下来,我们每次都触碰使直径减少\(2\)个点,最后只剩下\(1\)个或\(2\)个点,就一定可以取到\(\left\lfloor\frac{d+1}{2}\right\rfloor\)


现在我们来考虑下怎么求树的直径(如果会的大佬可以直接跳到代码部分)

我们首先随意定一个根节点\(x\),找到距离\(x\)最远的节点\(y\),再找到距离\(y\)最远的节点\(z\),此时,\(y\)\(z\)的距离就是树的直径

void work(int u,int fa)
{
	d[u]=d[fa]+1;
	if(d[u]>d[maxn])maxn=u;
	for(int i=head[u];i;i=nxt[i])
		if(to[i]!=fa)work(to[i],u);
}

代码

#include<bits/stdc++.h>
using namespace std;
const int N=5*1e5+10;
int n,cnt=0,tot=0;
int col[N];
int bok[N];
int to[N<<1];
int nxt[N<<1];
int head[N];
struct edge
{
	int u,v;
}e[N];
void add(int u,int v)
{
	to[++cnt]=v;
	nxt[cnt]=head[u];
	head[u]=cnt;
}
void dfs(int u,int fa)//缩点
{
	if(fa==0||col[u]!=col[fa])
	{
		bok[u]=++tot;
		if(fa)e[tot-1]=(edge){bok[u],bok[fa]};
	}
	else bok[u]=bok[fa];
	for(int i=head[u];i;i=nxt[i])
		if(to[i]!=fa)dfs(to[i],u);
}
int maxn=0;
int d[N];
void work(int u,int fa)
{
	d[u]=d[fa]+1;
	if(d[u]>d[maxn])maxn=u;
	for(int i=head[u];i;i=nxt[i])
		if(to[i]!=fa)work(to[i],u);
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&col[i]);
	int a,b;
	for(int i=1;i<n;i++)
	{
		scanf("%d %d",&a,&b);
		add(a,b);add(b,a);
	}
	dfs(1,0);
	cnt=0;
	memset(head,0,sizeof(head));
	for(int i=1;i<tot;i++)add(e[i].u,e[i].v),add(e[i].v,e[i].u);
	//开始求树的直径
	work(1,0);//找离节点1最远的节点
	int pos=maxn;
	maxn=0;
	work(pos,0);//找离节点pos最远的节点
	//求树的直径结束,d[maxn]为树的直径
	printf("%d",d[maxn]/2);
	return 0;
}
posted @ 2019-09-11 13:40  ShuraEye  阅读(179)  评论(0编辑  收藏  举报