• [构造][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;
    }
    
  • 相关阅读:
    layui自定义滚动条样式
    mongo 数据导入导出方法
    Go Map基本操作增删改查
    golang如何禁用gc?
    Go 切片操作
    Go 协程实现原理和使用示例
    GO语言中 json byte 与map 两种类型的转换
    解压 压缩命令
    Nginx 做转发解决跨域问题
    Go 操作文件 遍历目录获取文件列表
  • 原文地址:https://www.cnblogs.com/xyz32768/p/12333627.html
Copyright © 2020-2023  润新知