[构造][dfs树][树的重心][LOJ#3176]「IOI2019」景点划分

  • 题目传送门

  • 不妨假设 (ale ble c),注意到我们一定能够从连通块中不断删点得到更小的连通块,故可以钦定大小为 (a)(b) 的点集是连通的

  • 又由于大小分别为 (a)(b) 的两个连通块确定了之后,我们必然能让这两个连通块不断扩展使这两个点集包含所有 (n) 个点,所以问题转化成把原图划分成两个连通块,一个大小至少 (a),一个大小至少 (b)

  • 换句话说,这两个连通块中选出任意一个,必须满足其大小在 ([a,n-b]) 内或 ([b,n-a]) 内。若其中一个连通块满足条件则合法,其中一个连通块不满足条件则不合法

  • 先跑一遍 DFS 树,枚举树上每条边,如果这条边分割出的连通块满足条件则直接输出答案

  • 否则考虑 DFS 树的重心。注意到重心的每个子树大小都不在 ([a,n-b]) 内(否则前面已经被判定为合法),而由于 (a+b+c=n)(ale ble c),故 (blefrac n2),由重心的性质(每棵子树大小都不超过 (frac n2))得到重心每个子树的大小都 (<a)

  • 考虑重心的每个子树。如果一个子树内没有连向重心祖先(不包括重心本身)的返祖边,则这个子树必然和重心属于同一个连通块(否则必然无法让这棵子树所在连通块大小 (ge a)

  • 把所有这样的子树和重心合成一个连通块,如果这个连通块的大小大于 (n-a) 则无解

  • 否则构造一个初始解,重心的子树放在 (B) 集合,其余部分放在 (A) 集合

  • 显然,重心的子树大小是超过 (n-a) 的(因为重心上面的那一坨大小 (<a)

  • 依次把重心的所有有连向重心祖先返祖边的子树移到 (A) 集合内,直到 (B) 集合的大小 (le n-a) 为止

  • 如何证明这样得到的 (B) 集合大小 (ge b)

  • 证明:每次从 (B) 集合里删掉的子树大小一定 (<a),而区间 ([b,n-a]) 的大小为 (c+1),由 (ale c) 得到 (a) 必然小于区间 ([b,n-a]) 的大小,也就是说在 (B) 集合大小 (>n-a) 的情况下,一次无论删掉哪个子树都不会让 (B) 集合大小变得 (<b),得证

  • (O(n+m))

Code

#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	if (bo) res = ~res + 1;
}

template <class T>
inline T Max(const T &a, const T &b) {return a > b ? a : b;}

template <class T>
inline T Min(const T &a, const T &b) {return a < b ? a : b;}

const int N = 1e5 + 5, M = 4e5 + 5;

int n, m, a, b, c, ecnt, nxt[M], adj[N], sze[N], maxs[N], go[M], tr[4],
dep[N], ans[N], G = 1;
bool vis[N], ind[N];

std::vector<int> son[N];

void add_edge(int u, int v)
{
	nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v;
	nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u;
}

void dfs(int u)
{
	vis[u] = 1; sze[u] = 1;
	for (int e = adj[u], v; e; e = nxt[e])
		if (!vis[v = go[e]])
		{
			dep[v] = dep[u] + 1; dfs(v); sze[u] += sze[v];
			maxs[u] = Max(maxs[u], sze[v]);
			son[u].push_back(v);
		}
}

int calc(int u)
{
	int res = n + 1;
	for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
		res = Min(res, dep[v]);
	for (int i = 0; i < son[u].size(); i++)
		res = Min(res, calc(son[u][i]));
	return res;
}

void setr(int u, int c)
{
	ans[u] = c;
	for (int i = 0; i < son[u].size(); i++)
		setr(son[u][i], c);
}

void other(int c)
{
	for (int u = 1; u <= n; u++) if (!ans[u]) ans[u] = c;
}

void fake(int u, int s)
{
	vis[u] = 1; sze[u] = 1;
	for (int e = adj[u], v; e; e = nxt[e])
		if (!vis[v = go[e]] && ans[u] == ans[v])
		{
			fake(v, s); sze[u] += sze[v];
			if (s <= sze[v]) return;
			s -= sze[v];
		}
	if (s) ans[u] = 3;
}

void output(int ca, int cb)
{
	memset(vis, 0, sizeof(vis));
	for (int u = 1; u <= n; u++)
		if (ans[u] == 1) {fake(u, ca - a); break;}
	for (int u = 1; u <= n; u++)
		if (ans[u] == 2) {fake(u, cb - b); break;}
	for (int u = 1; u <= n; u++) printf("%d ", tr[ans[u]]);
	puts(""); exit(0);
}

int main()
{
	int x, y;
	read(n); read(m); read(a); read(b); read(c);
	tr[1] = 1; tr[2] = 2; tr[3] = 3;
	if (a > b) std::swap(a, b), std::swap(tr[1], tr[2]);
	if (a > c) std::swap(a, c), std::swap(tr[1], tr[3]);
	if (b > c) std::swap(b, c), std::swap(tr[2], tr[3]);
	while (m--) read(x), read(y), add_edge(x + 1, y + 1);
	dfs(dep[1] = 1);
	for (int u = 2; u <= n; u++)
	{
		if (a <= sze[u] && sze[u] <= n - b)
			setr(u, 1), other(2), output(sze[u], n - sze[u]);
		if (b <= sze[u] && sze[u] <= n - a)
			setr(u, 2), other(1), output(n - sze[u], sze[u]);
	}
	for (int u = 2; u <= n; u++)
		if (Max(maxs[u], n - sze[u]) < Max(maxs[G], n - sze[G]))
			G = u;
	int sum = 1;
	for (int i = 0; i < son[G].size(); i++)
		if (ind[i] = calc(son[G][i]) >= dep[G]) sum += sze[son[G][i]];
	if (sum > n - a) output(0, 0);
	setr(G, 2); other(1); sum = sze[G];
	for (int i = 0; i < son[G].size(); i++)
		if (!ind[i] && sum > n - a) sum -= sze[son[G][i]], setr(son[G][i], 1);
	return output(n - sum, sum), 0;
}
原文地址:https://www.cnblogs.com/xyz32768/p/12333627.html