【题解】【中山市选2009】树
\(\text{Description}\)
给定一棵树,每个节点有一盏灯和一个按钮。如果节点的按扭被按了,那么该节点的灯会从熄灭变为点亮(当按之前是熄灭的),或者从点亮到熄灭(当按之前是点亮的)。并且该节点的直接邻居也发生同样的变化。开始的时候,所有的灯都是熄灭的。请计算最少要按多少次按钮,才能让所有节点的指示灯变为点亮状态。
\(\text{Solution}\)
可以先看一看这道题 P1352 没有上司的舞会 以及 【题解】P1352。
对于这道题,我们也可以定义 \(dp_{u,0}\) 为节点 \(u\) 不亮时的最少次数,\(dp_{u,1}\) 为节点 \(u\) 亮时的最少次数,但是我们不好记录是否按了按钮。
所以改为三维:
-
\(dp_{u,0,0}\) 表示节点 \(u\) 不按按钮且不亮;
-
\(dp_{u,0,1}\) 表示节点 \(u\) 不按按钮且亮;
-
\(dp_{u,1,0}\) 表示节点 \(u\) 按按钮且不亮;
-
\(dp_{u,1,1}\) 表示节点 \(u\) 按按钮且亮。
在树形 \(\rm DP\) 时一定要满足子节点 \(v\) 是亮的。
\(u\) 的目标状态 | 需满足 | \(u\) 的原来状态 | \(v\) 的原来状态 |
---|---|---|---|
不按 不亮 | \(\to\) | 1. 不按 亮 2. 不按 不亮 | 1. 按 亮 2. 不按 亮 |
不按 亮 | \(\to\) | 1. 不按 不亮 2. 不按 亮 | 1. 按 亮 2. 不按 亮 |
按 不亮 | \(\to\) | 1. 按 亮 2. 按 不亮 | 1. 按 不亮 2. 不按 不亮 |
按 亮 | \(\to\) | 1. 按 不亮 2. 按 亮 | 1. 按 不亮 2. 不按 不亮 |
状态转移方程:
\(dp_{u,0,0}\gets\min(dp_{u,0,1}+dp_{v,1,1},dp_{u,0,0}+dp_{v,0,1});\)
\(dp_{u,0,1}\gets\min(dp_{u,0,0}+dp_{v,1,1},dp_{u,0,1}+dp_{v,0,1});\)
\(dp_{u,1,0}\gets\min(dp_{u,1,1}+dp_{v,1,0},dp_{u,1,0}+dp_{v,0,0});\)
\(dp_{u,1,1}\gets\min(dp_{u,1,0}+dp_{v,1,0},dp_{u,1,1}+dp_{v,0,0}).\)
这个其实挺难推的。
\(\text{Code}\)
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int MAXN = 105;
const int INF = 0x3f3f3f3f;
int cnt;
int head[MAXN], dp[MAXN][2][2];
struct edge
{
int to, nxt;
}e[MAXN << 1];
void add(int u, int v)
{
e[++cnt] = edge{v, head[u]};
head[u] = cnt;
}
void dfs(int u, int fa)
{
dp[u][0][0] = 0, dp[u][1][1] = 1, dp[u][0][1] = dp[u][1][0] = INF; //初始状态
for (int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].to;
if (v == fa)
{
continue;
}
dfs(v, u);
int t00 = min(dp[u][0][1] + dp[v][1][1], dp[u][0][0] + dp[v][0][1]); //我们是用上一次的来转移,所以提前存下来。
int t01 = min(dp[u][0][0] + dp[v][1][1], dp[u][0][1] + dp[v][0][1]);
int t10 = min(dp[u][1][1] + dp[v][1][0], dp[u][1][0] + dp[v][0][0]);
int t11 = min(dp[u][1][0] + dp[v][1][0], dp[u][1][1] + dp[v][0][0]);
dp[u][0][0] = min(t00, INF);
dp[u][0][1] = min(t01, INF);
dp[u][1][0] = min(t10, INF);
dp[u][1][1] = min(t11, INF);
}
}
int main()
{
int n;
while (scanf("%d", &n), n)
{
cnt = 0;
memset(head, 0, sizeof(head));
for (int i = 1; i < n; i++)
{
int u, v;
scanf("%d%d", &u, &v);
add(u, v);
add(v, u);
}
dfs(1, 0);
printf("%d\n", min(dp[1][0][1], dp[1][1][1])); //只选亮的
}
return 0;
}