COCI 2021-2022 #1 - Logičari 题解

题目大意

给定一个 n n n 个点的基环树,现在对基环树上的点染色,使得每个点都有且仅有一个与他相连的点(不包括它自身)被染色,求最少的染色点数,或者返回无解。

思路

先考虑树的情况。

容易想到 DP,我们设 f i , 1 / 0 , 1 / 0 f_{i,1/0,1/0} fi,1/0,1/0 表示在第 i i i 个点,它有没有染色,它的儿子中有没有染过色的。

很容易写出 DP 式。

由于是基环树,我们先把环找出来,然后对于环之外的树可以以环上的点为根先做树形DP(不包括环上的点),求出来对应的 f f f 值。

然后考虑环上,照样考虑 DP,设 g i , 1 / 0 , 2 / 1 / 0 g_{i,1/0,2/1/0} gi,1/0,2/1/0 表示在第 i i i 个点,它有没有染色,它是右边还是左边还是儿子的点染色。

但是由于是环,有后效性,于是我们可以枚举一个点的状态,然后当成链来做,最后将枚举到的最后一个点的合法状态的值取最小值即可。

代码

#include 
using namespace std;
int n, Ecnt, last[100005], rt, to, bz[100005], fa[100005], d[100005], f[100005][2][2], ans, g[100005][2][3];
struct Edge { int to, next; } E[200005];
void addedge(int u, int v) { Ecnt++, E[Ecnt].next = last[u], last[u] = Ecnt, E[Ecnt].to = v; }
void dfs(int x) {
    bz[x] = 1;
    for (int xy = last[x]; xy; xy = E[xy].next)
        if (!bz[E[xy].to])
            fa[E[xy].to] = x, d[E[xy].to] = d[x] + 1, dfs(E[xy].to);
}
void change(int x, int y) {
    bz[x] = 2;
    if (d[x] < d[y])
        swap(x, y);
    if (x == y) {
        rt = x;
        return ;
    }
    change(fa[x], y);
}
void DP(int x, int fa) {
    int son = 0;
    for (int xy = last[x]; xy; xy = E[xy].next)
        if (!bz[E[xy].to])
            son++, bz[E[xy].to] = 1, DP(E[xy].to, x), f[x][0][0] = f[x][0][0] + f[E[xy].to][0][1], f[x][1][0] = f[x][1][0] + f[E[xy].to][0][0];
    for (int xy = last[x]; xy; xy = E[xy].next)
        if (bz[E[xy].to] == 1 && E[xy].to != fa) {
            f[x][0][1] = min(f[x][0][1], f[x][0][0] - f[E[xy].to][0][1] + f[E[xy].to][1][1]);
            f[x][1][1] = min(f[x][1][1], f[x][1][0] - f[E[xy].to][0][0] + f[E[xy].to][1][0]);
        }
    f[x][1][0]++, f[x][1][1]++;
}
void get(int x, int fa) {
    for (int xy = last[x]; xy; xy = E[xy].next)
        if (bz[E[xy].to] == 2 && E[xy].to != fa && E[xy].to != to) {
            if (E[xy].to == rt) {
                to = x;
                return ;
            }
            for (int have = 0; have < 2; have++)
                for (int where = 0; where < 3; where++) {
                    if (have == 1)
                        g[E[xy].to][have][where] = min(g[E[xy].to][have][where], g[x][where == 1][2] + f[E[xy].to][have][where == 0]);
                    else
                        g[E[xy].to][have][where] = min(g[E[xy].to][have][where], min(g[x][where == 1][0], g[x][where == 1][1]) + f[E[xy].to][have][where == 0]);
                }
            get(E[xy].to, x);
        }
}
void solve() {
    for (int i = 1; i <= n; i++)
        f[i][1][1] = f[i][0][1] = n + 1;
    ans = n + 1;
    for (int i = 1; i <= n; i++)
        if (bz[i] == 2)
            DP(i, 0);//以环上的点为根做树形DP
    for (int have = 0; have < 2; have++) {
        for (int where = 0; where < 3; where++) {
            for (int i = 1; i <= n; i++)
                for (int j = 0; j < 3; j++)
                    g[i][0][j] = g[i][1][j] = n + 1;
            g[rt][have][where] = f[rt][have][where == 0], to = 0;
            get(rt, 0);
            if (have == 1)
                ans = min(ans, g[to][where == 1][2]);
            else
                ans = min(ans, min(g[to][where == 1][0], g[to][where == 1][1]));
        }
    }
    if (ans == n + 1)
        printf("-1");
    else
        printf("%d\n", ans);
}
int main() {
    scanf("%d", &n);
    for (int i = 1, u, v; i <= n; i++)
        scanf("%d%d", &u, &v), addedge(u, v), addedge(v, u);
    dfs(1);//先建树
    for (int i = 1; i <= n; i++)
        bz[i] = 0;
    for (int x = 1; x <= n; x++)
        for (int xy = last[x]; xy; xy = E[xy].next)
            if (fa[E[xy].to] != x && E[xy].to != fa[x]) {//找环
                bz[x] = bz[E[xy].to] = 2;
                change(x, E[xy].to);//标记环上的点
                solve();
                return 0;
            }
    return 0;
}

你可能感兴趣的:(题解,#,COCI,图论,算法,深度优先,c++,COCI)