Description \text{Description} Description
给定一棵树,每个节点有一盏灯和一个按钮。如果节点的按扭被按了,那么该节点的灯会从熄灭变为点亮(当按之前是熄灭的),或者从点亮到熄灭(当按之前是点亮的)。并且该节点的直接邻居也发生同样的变化。开始的时候,所有的灯都是熄灭的。请计算最少要按多少次按钮,才能让所有节点的指示灯变为点亮状态。
Solution \text{Solution} Solution
可以先看一看这道题 P1352 没有上司的舞会 以及 【题解】P1352。
对于这道题,我们也可以定义 d p u , 0 dp_{u,0} dpu,0 为节点 u u u 不亮时的最少次数, d p u , 1 dp_{u,1} dpu,1 为节点 u u u 亮时的最少次数,但是我们不好记录是否按了按钮。
所以改为三维:
-
d p u , 0 , 0 dp_{u,0,0} dpu,0,0 表示节点 u u u 不按按钮且不亮;
-
d p u , 0 , 1 dp_{u,0,1} dpu,0,1 表示节点 u u u 不按按钮且亮;
-
d p u , 1 , 0 dp_{u,1,0} dpu,1,0 表示节点 u u u 按按钮且不亮;
-
d p u , 1 , 1 dp_{u,1,1} dpu,1,1 表示节点 u u u 按按钮且亮。
在树形 D P \rm DP DP 时一定要满足子节点 v v v 是亮的。
u u u 的目标状态 | 需满足 | u u u 的原来状态 | v v v 的原来状态 |
---|---|---|---|
不按 不亮 | → \to → | 1. 不按 亮 2. 不按 不亮 | 1. 按 亮 2. 不按 亮 |
不按 亮 | → \to → | 1. 不按 不亮 2. 不按 亮 | 1. 按 亮 2. 不按 亮 |
按 不亮 | → \to → | 1. 按 亮 2. 按 不亮 | 1. 按 不亮 2. 不按 不亮 |
按 亮 | → \to → | 1. 按 不亮 2. 按 亮 | 1. 按 不亮 2. 不按 不亮 |
状态转移方程:
d p u , 0 , 0 ← min ( d p u , 0 , 1 + d p v , 1 , 1 , d p u , 0 , 0 + d p v , 0 , 1 ) ; dp_{u,0,0}\gets\min(dp_{u,0,1}+dp_{v,1,1},dp_{u,0,0}+dp_{v,0,1}); dpu,0,0←min(dpu,0,1+dpv,1,1,dpu,0,0+dpv,0,1);
d p u , 0 , 1 ← min ( d p u , 0 , 0 + d p v , 1 , 1 , d p u , 0 , 1 + d p 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}); dpu,0,1←min(dpu,0,0+dpv,1,1,dpu,0,1+dpv,0,1);
d p u , 1 , 0 ← min ( d p u , 1 , 1 + d p v , 1 , 0 , d p u , 1 , 0 + d p v , 0 , 0 ) ; dp_{u,1,0}\gets\min(dp_{u,1,1}+dp_{v,1,0},dp_{u,1,0}+dp_{v,0,0}); dpu,1,0←min(dpu,1,1+dpv,1,0,dpu,1,0+dpv,0,0);
d p u , 1 , 1 ← min ( d p u , 1 , 0 + d p v , 1 , 0 , d p u , 1 , 1 + d p 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}). dpu,1,1←min(dpu,1,0+dpv,1,0,dpu,1,1+dpv,0,0).
这个其实挺难推的。
Code \text{Code} 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;
}