牛客周赛 Round 89——小红开灯

题目

小红开灯(五,easy)
小红开灯(五,hard)

题解

这题是一道树形dp题,关键的难点就在于状态方程该如何考虑,我们可以设dp[N][3],如果当前点u操作,会和他的父亲一起染色,因为父亲只有一个,儿子不一定只有一个,选择哪个儿子染可能会导致更难的问题出现。我们可以发现一种方案,如果一个点被染过色,那么他不需要被再次改变颜色,对于此题,两次开关相当于没有任何操作,所以至多改变一次是最优操作。由此我们可以得出来两个结论

如果点u进行操作,那么他的儿子不可能进行操作。
如果点u不进行操作,他的儿子如果全被染色了,那一定是他们儿子的儿子干的。

由此我们可以得出状态表示的含义以及状态转移方程。

f[u][0] u进行操作,u和u的父亲染色。
由于结论1可知,他不可能由f[v][0]而来,但他均可从其他两个状态转移过来。故状态方程为f[u][0] += min(f[v][1], f[v][2]);
f[u][1] u不进行操作,但他其中一个儿子进行操作,导致u的其中一个儿子和u本身染色。
他由其中一个f[v][0]转移过来,对于其他儿子,f[v][1]、f[v][2]操作也均可,我们需要找出最小的那个f[v][0],这便是这道题的最难点所在。我们可以先进行f[u][1] += min(f[v][1], f[v][2])操作,然后再找出最小的f[v][0] - min(f[v][1], f[v][2]) 给他加回去,那么我们就将他染色的来源变成了f[v][0]。详见代码。
f[u][2] u不进行操作,他的所有儿子也均未操作,但是题目又不能有相邻两个亮的状态,那么就说明是由于他的儿子是由于他儿子的儿子导致染色也就是f[v][1], 状态转移方程为 f[u][2] += f[v][1]。

这样子我们就解决easy版了,只需找到f[1][1]和f[1][2]中的最小值输出即可。但对于hard版还要输出他的操作方案,我们就需要记录他从哪些为0的位置操作的。所以我们要记录f[u][1]时是由哪个儿子导致的u变色。然后我们再dfs一遍,遇到0就把他和他的父亲存起来,然后接着往下找就行了,最后输出即可。下面直接呈现hard版代码。

参考代码

#include<iostream>
#include<vector>
using namespace std;
typedef pair<int, int> PII;
const int N = 1e5 + 10;
#define int long long
int n;
vector<int> g[N];
int f[N][3], son[N];
vector<PII> ans;
void dfs(int u, int fa) {
    f[u][0] = 1;
    int ma = 1e9, id;
    for(auto v : g[u]) {
        if(v == fa) continue;
        dfs(v, u);
        f[u][0] += min(f[v][1], f[v][2]);
        f[u][1] += min(f[v][1], f[v][2]);
        f[u][2] += f[v][1];
        if(ma > f[v][0] - min(f[v][1], f[v][2])) {
            ma = f[v][0] - min(f[v][1], f[v][2]);
            id = v;
        }
    }
    
    if(g[u].size() != 1 || u == 1) f[u][1] += ma, son[u] = id;
    if(g[u].size() == 1 && u != 1) f[u][1] = 1e9;
}
void dfs2(int u, int fa, int k) {
    if(k == 0) ans.push_back({u, fa});
    for(auto v : g[u]) {
        if(v == fa) continue;
        if(k == 0) {
            if(f[v][1] < f[v][2]) dfs2(v, u, 1);
            else dfs2(v, u, 2);
        } else if(k == 1) {
            if(v == son[u]) dfs2(v, u, 0);
            else {
                if(f[v][1] < f[v][2]) dfs2(v, u, 1);
                else dfs2(v, u, 2);
            }
        } else dfs2(v, u, 1);
    }
}
signed main() {
    cin >> n;
    for(int i = 1; i < n; i ++) {
        int u, v;
        cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    dfs(1, -1);
    cout << min(f[1][1], f[1][2]) << endl;
    if(f[1][1] < f[1][2]) dfs2(1, -1, 1);
    else dfs2(1, -1, 2);
    for(auto u : ans) cout << u.first << " " << u.second << endl;
    return 0;
}
posted @ 2025-04-15 22:56  PZnwbh  阅读(21)  评论(0)    收藏  举报