树形DP
好久没写正常的题解了,于是写一发。
刚开始想这题的转移的时候乱糟糟的,因为一个点的儿子可能很多,转移起来就很麻烦。其实树形DP的本质是有一棵树,每次只加入一棵子树。这样想起来就方便多了。
记f[i][j=0/1/2/3/4][k=0/1/2]表示以i为根的子树,j=0表示i和i子树中与i相邻的点都没有放,j=1和j=2分别表示i这个点放了1或2个,j=3和j=4分别表示i这个点没放但i子树中与i相邻的点放了1或2个,k表示与i相邻的点中还缺了几个,即至少还要放几个。
最麻烦的地方就是状态之间的转移,手推清楚即可。
#include<cstdio>
#include<algorithm>
#define N 200005
using namespace std;
const int INF = 1 << 29;
int n, last[N], ecnt, f[N][5][3], h[5][3];
struct edge{int next, to;}e[N<<1];
void addedge(int a, int b)
{
e[++ecnt] = (edge){last[a], b};
last[a] = ecnt;
}
bool trans(int j, int k, int a, int b, int &x, int &y)
{
if(b == 1 && j != 1 && j != 2) return false;
if(b == 2 && j != 2) return false;
int nk;
if(j == 1 || j == 2 || j == 4 || a == 1 || a == 2 || a == 4) nk = 0;
else if(j == 3 && a == 3) nk = 0;
else if(j == 0 && a == 3) nk = 1;
else if(j == 3 && a == 0) nk = 1;
else nk = 2;
if(a == 1) k-=1;
else if(a == 2) k-=2;
y = max(k, nk);
if(j == 1 || j == 2) x = j;
else
{
if(j == 0) x = 2;
else x = j;
if(a == 1) x += 1;
else if(a == 2) x += 2;
if(x == 2) x = 0;
else x = min(x, 4);
}
return true;
}
void dfs(int x, int fa)
{
for(int j = 0; j < 5; j++) for(int k = 0; k < 3; k++) f[x][j][k] = INF;
f[x][0][0] = 0; f[x][1][0] = 1; f[x][2][0] = 2;
for(int i = last[x]; i; i = e[i].next)
{
int y = e[i].to; if(y == fa) continue;
dfs(y, x);
for(int j = 0; j < 5; j++) for(int k = 0; k < 3; k++) h[j][k] = INF;
for(int j = 0; j < 5; j++) for(int k = 0; k < 3; k++) if(f[x][j][k] < INF)
for(int a = 0; a < 5; a++) for(int b = 0; b < 3; b++) if(f[y][a][b] < INF)
{
int c, d;
if(trans(j, k, a, b, c, d))
h[c][d] = min(h[c][d], f[x][j][k] + f[y][a][b]);
}
for(int j = 0; j < 5; j++) for(int k = 0; k < 3; k++) f[x][j][k] = h[j][k];
}
}
int main()
{
scanf("%d",&n);
for(int i = 1; i < n; i++)
{
int a, b; scanf("%d%d",&a,&b);
addedge(a, b); addedge(b, a);
}
dfs(1,0);
int ans = INF;
for(int j = 0; j < 5; j++) ans = min(ans, f[1][j][0]);
printf("%d\n",ans);
}