很好的一道树上DP
我会告诉你我想那个“-2”想了一个下午吗?
题面:
一场可怕的地震后,人们用N个牲口棚(1≤N≤150,编号1..N)重建了农夫John的牧场。由于人们没有时间建设多余的道路,所以现在从一个牲口棚到另一个牲口棚的道路是惟一的。因此,牧场运输系统可以被构建成一棵树。John想要知道另一次地震会造成多严重的破坏。有些道路一旦被毁坏,就会使一棵含有P(1≤P≤N)个牲口棚的子树和剩余的牲口棚分离,John想知道这些道路的最小数目。
输入:
第1行:2个整数,N和P
第2..N行:每行2个整数I和J,表示节点I是节点J的父节点。
输出:
单独一行,包含一旦被破坏将分离出恰含P个节点的子树的道路的最小数目。
分析:
(dp[i][j])表示计算至(i)时,分离出整颗大小为(j)的子树(包括(i))所需切掉的最少道路数。
初始化一个点的时候,把它的所有连边给去掉,(dp[i][1]=du[i])。
父节点每与一个子节点合并,需要将dp之和-2,为什么?
因为在初始化时,已经将u的(u->v)去掉,也已经将(v)的(v->u)去掉,而如果想将含(u)连通块与含(v)连通块相连,这条边不能被去掉。
然后我一直在想为什么每一次都要减去2
答案是:我们的dp操作规则是默认一开始所有的点都被切开,所以我们的运算要遵循这个规则,每一次计算也就是一次合并,无论哪两个dp值都遵循着不与外界联系的规律,所以两个dp值相加一定要-2.
code:
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int INF=0x3f3f3f3f;
const int N=201;
struct Edge{
int to,next;
}e[N*2];
int du[N],a[N],dp[N][N];
int n,k,res=INF,EdgeCnt=0;
void addedge(int u,int v){
int p=++EdgeCnt;
e[p].to=v;e[p].next=a[u];
a[u]=p;
}
void dfs(int u,int fa){
dp[u][1]=du[u];
for (int p=a[u];p;p=e[p].next){
int v=e[p].to;
if (v!=fa){
dfs(v,u);
for (int j=k;j>=1;j--)
for (int k=1;k<=j;k++)
dp[u][j]=min(dp[u][j],dp[u][j-k]+dp[v][k]-2);
}
}
res=min(res,dp[u][k]);
}
int main(){
scanf("%d%d",&n,&k);
memset(dp,0x3f,sizeof(dp));
for (int i=1;i<n;i++){
int u,v;
scanf("%d%d",&u,&v);
addedge(u,v);
addedge(v,u);
du[u]++;du[v]++;
}
dfs(1,0);
printf("%d",res);
return 0;
}