洛谷P1272 重建道路(树形DP+分组背包)
题目描述
一场可怕的地震后,人们用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个节点的子树的道路的最小数目。
输入输出样例
输入 #1
11 6 1 2 1 3 1 4 1 5 2 6 2 7 2 8 4 9 4 10 4 11
输出 #1
2
不是很明白为啥题解都热衷于建无向图...这明明是一棵有根树啊...直接找到根节点dfs就可以了。
dp[i][j]:i这个节点得到j个节点的子树(包括自己)且与i节点的父亲节点相连所需要删除的最少边数 转移方程在熟悉的套路上稍作改动: dp[x][j]=min(dp[x][j],dp[x][j-k]+dp[y][k]-1);因为在初始化的时候都是与儿子不相连的,去掉的边也累计进去了,但转移的时候要连起来因此要把这一条边补上。
还有就是最后答案是max(dp[i][p])而非dp[root][p]。
#include <bits/stdc++.h> #define N 310 using namespace std; int n,p,head[N],ver[N],Next[N],tot=0,size[N]={0}; int dp[N][N]={0};//dp[i][j]:i这个节点得到j个节点的子树(包括自己)且与i节点的父亲节点相连所需要删除的最少边数 bool vis[N]={0}; void add(int x,int y) { ver[++tot]=y,Next[tot]=head[x],head[x]=tot; } void dfs(int x) { //初始化 dp[x][1]=size[x];//与所有儿子断绝关系 int i,j,k; for(i=head[x];i;i=Next[i]) { int y=ver[i]; dfs(y); for(j=p;j>=1;j--)//背包容量 其实直接从p开始就好 没必要从前xx棵子树的节点总数开始 从大到小枚举是因为压缩掉了 前xx棵子树 这一维度 { for(k=1;k<j;k++) { dp[x][j]=min(dp[x][j],dp[x][j-k]+dp[y][k]-1); } } } } int main() { cin>>n>>p; int i; memset(dp,0x3f3f3f3f,sizeof(dp));//注意初始化 memset(size,0,sizeof(size)); memset(vis,0,sizeof(vis)); for(i=1;i<=n-1;i++) { int x,y; scanf("%d%d",&x,&y); add(x,y); size[x]++;//儿子个数 vis[y]=1;//是儿子的就标记上 } int root; for(i=1;i<=n;i++) { if(vis[i]==0) { root=i; break; } dp[i][1]=size[i]; } dfs(root); int ans=dp[root][p]; for(i=1;i<=n;i++) { if(dp[i][p]<ans)ans=dp[i][p]+1; } cout<<ans;//有问题 }