【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;
}