重建道路
题目:
Description
一场可怕的地震后,人们用N个牲口棚(1≤N≤150,编号1..N)重建了农夫John的牧场。由于人们没有时间建设多余的道路,所以现在从一个牲口棚到另一个牲口棚的道路是惟一的。因此,牧场运输系统可以被构建成一棵树。John想要知道另一次地震会造成多严重的破坏。有些道路一旦被毁坏,就会使一棵含有P(1≤P≤N)个牲口棚的子树和剩余的牲口棚分离,John想知道这些道路的最小数目.
Input
第1行:2个整数,N和P;第2..N行,每行2个整数I和J,表示节点I是节点J的父节点。
Output
单独一行,也包含一旦被破坏将分离出恰含P个节点的子树的道路的最小数目。
树形DP (用了两种方式)
一:边集数组
- f[x][p]表示以x为根的子树保留p个点要删除的最小边数
- 状态转移方程:f[x][j]=min(f[x][j],f[t[i].v][k]+f[x][j-k]-2)
- 转移时枚举当前点删除多少边,儿子节点能删除多少条边
- 因为由儿子转移过来,他们之间的连边不能断,but转移时断掉了两次,所以ans-2
代码
#include <iostream> #include <cstdio> #include <cstring> #include <cstdlib> using namespace std; const int N=305,M=100000000; int n,p,a[N],d[N],f[N][N],tot,ans; struct ss { int nx,v; }t[N*2]; void add(int u,int v)//边集数组 { tot++; t[tot].v=v; t[tot].nx=a[u]; a[u]=tot; } void dp(int x,int fa)//x为当前节点,fa为当前节点的父节点 { f[x][1]=d[x]; //x节点保留1个节点时将与x节点相连的子节点断开 int i,j,k; for (k=a[x]; k; k=t[k].nx) if (t[k].v!=fa) { dp(t[k].v,x); for (i=p; i>=1; i--) for (j=1; j<=i; j++) f[x][i]=min(f[x][i],f[t[k].v][j]+f[x][i-j]-2); } ans=min(ans,f[x][p]); } int main() { memset(f,0x03f,sizeof(f)); scanf("%d%d",&n,&p); int x,y; for (int i=1; i<n; i++) { scanf("%d%d",&x,&y); d[x]++; d[y]++; add(x,y); add(y,x); } ans=M; dp(1,0); printf("%d\n",ans); return 0; }
二:f[x][p]表示以x为根的子树保留p个点要删除的最小边数
- res[rx][p]表示x节点右边最近的节点保留p个点要删除的最小边数
- 上一种方式相比,多用res数组记录兄弟节点的值,而上一种则是从p至1循环防止前面更新的值对后面的值产生影响
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> using namespace std; const int N=305,M=1000000000; int a[N],nx[N],f[N][N],res[N][N]; int n,p,root,ans=M; bool flag[N]; void dfs(int x) { for (int i=0; i<=p; i++) f[x][i]=res[x][i]=M; f[x][0]=1; if (a[x]==0) { f[x][1]=0; return; } int i,j,k,rk=0; for (k=a[x]; k; k=nx[k]) { dfs(k); if (rk>0) for (i=0; i<=p; i++) for (j=0; j<=i; j++) res[k][i]=min(res[k][i],res[rk][j]+f[k][i-j]); else for (i=0; i<=p; i++) res[k][i]=f[k][i]; rk=k; } for (i=1; i<=p; i++) f[x][i]=res[rk][i-1]; } int main() { scanf("%d%d",&n,&p); int u,v; for (int i=1; i<n; i++) { scanf("%d%d",&u,&v); nx[v]=a[u]; a[u]=v; flag[v]=true; } for (int i=1; i<=n; i++) if (flag[i]==false) {root=i; break;} dfs(root); for (int i=1; i<=n; i++) if (i==root) ans=min(ans,f[i][p]); else ans=min(ans,f[i][p]+1); printf("%d\n",ans); return 0; }