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

  • 题目传送门

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

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

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

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

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

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

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

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

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

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

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

  • 证明:每次从 \(B\) 集合里删掉的子树大小一定 \(<a\),而 \(a\le b\le c\) 保证了 \(a\le\frac n3\)\(c\ge\frac n3\),而区间 \([b,n-a]\) 的大小就是 \(c+1\),故 \(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;
}

猜你喜欢

转载自www.cnblogs.com/xyz32768/p/12333627.html