【题解】「一本通 5.2 练习 4」叶子的染色
题面
题目描述
原题来自:CQOI 2009
给一棵有 \(m\) 个节点的无根树,你可以选择一个度数大于 \(1\) 的节点作为根,然后给一些节点(根、内部节点、叶子均可)着以黑色或白色。你的着色方案应保证根节点到各叶子节点的简单路径上都包含一个有色节点,哪怕是叶子本身。
对于每个叶子节点 \(u\) ,定义 \(c_u\) 为从根节点到 \(u\) 的简单路径上最后一个有色节点的颜色。给出每个 \(c_u\) 的值,设计着色方案使得着色节点的个数尽量少。
输入格式
第一行包括两个数 \(m\) , \(n\) ,依次表示节点总数和叶子个数,节点编号依次为 \(1\) 至 \(m\) 。
接下来 \(n\) 行每行一个 \(0\) 或 \(1\) 的数,其中 \(0\) 表示黑色,\(1\) 表示白色,依次为 \(c_1\) , \(c_2\) , \(\cdots\) , \(c_n\) 的值。
接下来 \(m-1\) 行每行两个整数 \(a\) , \(b\) ,表示节点 \(a\) 与 \(b\) 有边相连。
输出格式
输出仅一个数,表示着色节点数的最小值。
样例
样例输入
5 3
0
1
0
1 4
2 5
4 5
3 5
样例输出
2
数据范围与提示
对于全部的测试点,保证 \(1\leqslant m\leqslant 10^4\) ,\(1\leqslant n\leqslant 5021\),\(1\leqslant a < b \leqslant m\) 。
Solution
题意简述
题目翻译以避雷:有一棵共有 \(m\) 个结点的树,叶子结点的编号已规定为 \(1\) 到 \(n\) ,每个叶子节点到根节点的唯一简单路径上至少有一个被染色的结点,输入离第 \(i\) 个结点最近的染色节点的颜色 \(c_i\) 和树的结构,求最少的染色结点个数。
备注
显而易见的树形DP。
俗话说得好:"因为这题是一棵树,又要用 \(DP\) 解,所以我们知道这道题是一道树形DP。"
为了更加契合我的代码,我们将 \(n\) 与 \(m\) 所表示的含义交换。(主要是作者比较喜欢用 \(n\) 作总数)
设计状态
f[i][j] 表示将编号为 i 的节点涂成 j 颜色时,满足条件最少需要涂色的节点数。
状态转移方程
我们假设 \(x\) 为某一个父子关系中的父节点, \(e\) 为子节点。
不考虑结点本身需要的颜色,如果 \(x\) 染成 \(a\) 色,那么 \(e\) 就不用染成 \(a\) 色,因为此时 \(x\) 一定是距离 \(e\) (和 \(e\) 的儿子,因为他们的父亲 \(e\) 此时是没有染色的)最近的染成 \(a\) 色的祖先。
由于 \(f_{e,a}\) 表示的是加上 \(e\) 染色时的最小值,那么我们只要用 \(f_{x,a}\) (也就是 \(x\) 本身染色的 \(1\) 加上已处理好的 \(x\) 的其他儿子的值)加上 \(f_{e,a}-1\) (也就是 \(e\) 子树所有儿子中的染色节点个数,\(e\) 不染色所以要减一)就可以算出最新的 \(f_{x,a}\) 的值了。
如果 \(x\) 染成 \(a\) 色,\(e\) 染成 \(b\) 色呢?为了避免 \(e\) 继承 \(x\) 的颜色,我们还是要将 \(x\) 和 \(e\) 都染色,用 \(f_{x,a}+f_{e,b}\) 就珂以哒!
至此,不难推出状态转移方程柿子:
f[x][0]+=min(f[e][0]-1,f[e][1]);
f[x][1]+=min(f[e][0],f[e][1]-1);
初始化
将每个非叶子节点的每个颜色都初始化为 \(1\)。
因为在没有明确计算出子树和父亲的值时,每个需要染色的结点有且仅有当前节点本身而已。
对于每个叶子结点,我们将题目中限定颜色的 \(dp\) 值初始化为 \(1\) ,原因同上。
而题目中限定颜色的相反色初始化为 \(\infty\) ,以避免被 \(\min\) 取走。
根节点
根据我们的状态转移方程来看,每个 \(e\) 涂不涂色仅与相邻的 \(x\) 父亲有关,不会出现 "值本来与祖先有关,但是因为根节点不同,导致祖先变成了同辈或后辈,影响本身的值" 的情况,所以只要随便选一个不是叶子结点的结点就好哒!
Code
//好像没什么可注释的...
#include<cstdio>
const int maxn=10005;
const int maxm=maxn<<1;
const int inf=0x3f3f3f3f;
int f[maxn][2];
int n,m,tot,x,y,root;
int c[maxn],h[maxn],to[maxm],nxt[maxm];
int min(int x,int y){
return x<y?x:y;
}
void read(int&x){
x=0;
bool f=0;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')f=1;
ch=getchar();
}
while(ch<='9'&&ch>='0'){
x=(x<<1)+(x<<3)+(ch&15);
ch=getchar();
}
if(f)x=-x;
return;
}
void add(int x,int y){
to[++tot]=y;
nxt[tot]=h[x];
h[x]=tot;
to[++tot]=x;
nxt[tot]=h[y];
h[y]=tot;
return;
}
void dfs(int x,int fa){
for(int i=h[x];i;i=nxt[i]){
int e=to[i];
if(e==fa)continue;
dfs(e,x);
f[x][0]+=min(f[e][0]-1,f[e][1]);
f[x][1]+=min(f[e][0],f[e][1]-1);
}
return;
}
int main(){
read(n);read(m);
for(int i=1;i<=m;++i){
read(c[i]);
f[i][c[i]]=1;
f[i][!c[i]]=inf;
}
for(int i=1;i<n;++i){
read(x);read(y);
add(x,y);
}
for(int i=m+1;i<=n;++i)
f[i][0]=f[i][1]=1;
dfs(n,-1); //本来之前我也不知道可以随便选根,据说 n 当根可以过,我就用 n 做根了www
printf("%d",min(f[n][0],f[n][1]));
return 0;
}
end.
—— · EOF · ——
真的什么也不剩啦 😖