叶子的颜色

叶子的颜色

给一棵有 m 个节点的无根树,你可以选择一个度数大于 1 的节点作为根,然后给一些节点(根、内部节点、叶子均可)着以黑色或白色。

你的着色方案应保证根节点到各叶子节点的简单路径上都至少包含一个有色节点,哪怕是这个叶子本身。

对于每个叶子节点 u,定义 cu 为从根节点到 u 的简单路径上最后一个有色节点的颜色。

给出每个 cu 的值,设计着色方案使得着色节点的个数尽量少。

输入格式

第一行包括两个数 m,n,依次表示节点总数和叶子个数,节点编号依次为 1m,其中编号 1n 是叶子。

接下来 n 行每行一个 01 的数,其中 0 表示黑色,1 表示白色,依次为 c1,c2,,cn 的值。

接下来 m1 行每行两个整数 a,b,表示节点 ab 有边相连。

输出格式

输出仅一个数,表示着色节点数的最小值。

数据范围

数据 1 2 3 4 5 6 7 8 9 10
M 10 50 100 200 400 1000 4000 8000 10000 10000
N 5 23 50 98 197 498 2044 4044 5021 4996

输入样例:

5 3
0
1
0
1 4
2 5
4 5
3 5

输出样例:

2

 

解题思路

  首先一定是有解的,先随便选择一个度数大于1的节点作为整棵树的根节点,然后直接对每个叶子节点u涂上对应的颜色cu即可。不过可以发现这肯定不是最优的解法,假如有k个叶子的父节点相同,并且这k个叶子节点中有x个被染成白色,y个染成黑色(满足x+y=k),那么我们可以把这个父节点染成xy中最大的那个所对应的颜色,然后再将这个颜色的叶子改成透明,这样染色的节点数目就变成了kmax{x,y}+1,比原来的k要小。以此类推,从叶子不断往上重复这个步骤,直到根节点。

  以样例为例,选取5号点作为根节点,如下图:

  很明显当儿子染成白色和黑色的数目不相同时,父亲应该染成颜色最多的那个更优。而如果儿子染成黑白的数目相同时,父亲染成这两种颜色都是最优解,这时情况就不唯一了,最终父亲要染成什么颜色还要取决于与它同层的兄弟节点染成什么颜色最优。这当然可以通过枚举所有的情况然后取最优解,但我们可以用dp来避免重复枚举。

  可以发现当我们往上刚走到父节点时,此时所有的儿子都是染了色的,经过操作后父节点也必然会染色(相应的儿子改为透明)。因此定义状态f(u,j)表示考虑以u为根的子树,且u被染成颜色j时的所有合法方案的染色最小数目。由于u的每棵子树是相互独立的,且儿子vi也必然染了颜色,因此状态转移方程就是f(u,j)=1+i=1kmin{f(vi,j)1,f(vi,j¯)}

  其中加1是因为u要染色,f(vi,j)1表示儿子与父亲染成一样的颜色,因此儿子改成透明,f(vi,j¯)表示儿子与父亲染的颜色不同。

  最后我们只需任选一个度数大于1的节点作为根节点跑一遍dfs即可,因为对于任意一个根节点分析的方法是一样的,得到的最优解也是一样的。

  AC代码如下,时间复杂度为O(n+m)

复制代码
 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int N = 1e4 + 10, M = N * 2;
 5 
 6 int n, m;
 7 int c[N];
 8 int head[N], e[M], ne[M], idx;
 9 int f[N][2];
10 
11 void add(int v, int w) {
12     e[idx] = w, ne[idx] = head[v], head[v] = idx++;
13 }
14 
15 void dfs(int u, int pre) {
16     if (u <= m) {
17         f[u][c[u]] = 1;
18         return;
19     }
20     f[u][0] = f[u][1] = 1;
21     for (int i = head[u]; i != -1; i = ne[i]) {
22         if (e[i] != pre) {
23             dfs(e[i], u);
24             f[u][0] += min(f[e[i]][0] - 1, f[e[i]][1]);
25             f[u][1] += min(f[e[i]][1] - 1, f[e[i]][0]);
26         }
27     }
28 }
29 
30 int main() {
31     scanf("%d %d", &n, &m);
32     for (int i = 1; i <= m; i++) {
33         scanf("%d", c + i);
34     }
35     memset(head, -1, sizeof(head));
36     for (int i = 0; i < n - 1; i++) {
37         int v, w;
38         scanf("%d %d", &v, &w);
39         add(v, w), add(w, v);
40     }
41     memset(f, 0x3f, sizeof(f));
42     dfs(n, -1);
43     printf("%d", min(f[n][0], f[n][1]));
44     
45     return 0;
46 }
复制代码

 

参考资料

  [CQOI2009] 叶子的颜色 解题报告(树形DP) :https://www.cnblogs.com/xxzh/p/9278487.html

  AcWing 1079. 叶子的颜色(蓝桥杯集训·每日一题):https://www.acwing.com/video/4671/

posted @   onlyblues  阅读(17)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
Web Analytics
点击右上角即可分享
微信分享提示