Codeforces 734E
题意:有一棵树被黑白染色,同色的为一个联通块,每次可以让一个联通块变色,然后和周围颜色相同的联通块组成大的联通块,求最少多少次能让所有联通块变为同色
联通块直接 DFS 缩点, 然后新图变为黑白相间的树。考虑树的直径有下列性质:
以树的直径中点上的一点为根,将这颗无根树转化为有根树的话,这样做会使树的深度最小,且为树的直径的一半(向上取整)(因为如果有一个叶子节点比直径更深的话,那么就可以构成一个更长的直径,与假设矛盾。)
所以答案是$(d+1)/2$, $d$为直径长。因为按照树的直径中点为根,每次操作直径中点改变颜色,改变$(d+1)/2$次就能改为同色。画图理解。
不要犯了老错误, CF 909E就误以为0点只有一层而错误。此题因为没有考虑到“一个联通块变色,然后和周围颜色相同的联通块组成大的联通块”,以为比较黑白联通块个数,造成了错误。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<vector>
#include<queue>
#define ms(i, j) memset(i, j, sizeof i)
#define LL long long
#define db double
using namespace std;
const int MAXN = 200000 + 5;
vector<int> G[MAXN], G2[MAXN];
int n, col[MAXN], bl[MAXN], sz, vis[MAXN], dis[MAXN];
queue<int> q;
void ins(int x, int y) {G[x].push_back(y), G[y].push_back(x);}
void ins2(int x, int y) {G2[x].push_back(y), G2[y].push_back(x);}
void dfs(int u, int pa, int cha) {
bl[u] = cha;
for (int i = 0; i < (int)G[u].size(); i++) {
int v = G[u][i];
if (v != pa) {
if (col[v] != col[u]) dfs(v, u, ++sz);
else dfs(v, u, cha);
}
}
}
void clean() {
ms(vis, 0), sz = 0;
}
int solve() {
clean();
for (int i = 1; i <= n; i++) scanf("%d", &col[i]);
for (int x, y, i = 1; i < n; i++) scanf("%d%d", &x, &y), ins(x, y);
dfs(1, 0, ++sz);
for (int u = 1; u <= n; u++) {
for (int i = 0; i < (int)G[u].size(); i++) {
int v = G[u][i];
ins2(bl[u], bl[v]);
}
}
q.push(1), vis[1] = 1, dis[1] = 0;
int mks = 0, pos = 1;
while (!q.empty()) {
int u = q.front(); q.pop();
for (int i = 0; i < (int)G2[u].size(); i++) {
int v = G2[u][i];
if (!vis[v]) {
vis[v] = 1, dis[v] = dis[u] + 1, q.push(v);
if (dis[v] > mks) mks = dis[v], pos = v;
}
}
}
ms(vis, 0);
q.push(pos), vis[pos] = 1, dis[pos] = 0, mks = 0;
while (!q.empty()) {
int u = q.front(); q.pop();
for (int i = 0; i < (int)G2[u].size(); i++) {
int v = G2[u][i];
if (!vis[v]) {
vis[v] = 1, dis[v] = dis[u] + 1, q.push(v);
if (dis[v] > mks) mks = dis[v];
}
}
}
printf("%d\n", (mks + 1) / 2);
return 0;
}
int main() {
scanf("%d", &n), solve();
return 0;
}