P1272 重建道路

題目描述

一場可怕的地震後,人們用\(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也卡了我好長時間。


首先明確一點(卡了我好久),這是一顆樹,根是可以隨便選的。

如果只按照題目把樹輸入是會大的(然而資料水只錯了兩個點)


令\(dp[i][j]\)代表根節點為\(i\)的子樹在失去(注意是失去)\(j\)個點時 切掉的邊的最小個數。

\(dp[i][j]=min(dp[i][j],dp[i][j-k]+dp[i_{son}][k])\)

  • 注意:這個已經滾動掉了一維(第幾個子樹) 列舉\(j\)時要倒著哦

有個問題,對於直接切掉自己兒子的情況怎麼辦?

最開始時我這樣做了,在每次列舉\(j\)的時候

\(dp[i][j]=min(dp[i][j],dp[i][j-size[i_{son}]]+1);\)

然而這樣做是重複了的。(想想看)

有個很喵(妙)的做法。

在\(dfs\)每次求完\(i\)時把她自己切掉就好了啦 -> \(dp[i][size[i]]=1\);

回到最開始,雖然根不確定,其實只需要隨便取一種根的情況就行拉。

比如說\(root\)是根,\(dp[root][n-p]\)肯定不包含切她自己的情況

我們就在其他節點中,把自己切成一棵樹,即用\(dp[i][p-(n-size[i])]+1\)更新\(ans\)

#include <cstdio>
#include <cstring>
int min(int x,int y) {return x<y?x:y;}
const int N=156;
int dp[N][N];//子樹i捨棄j個點的最小切割數
int n,p,root;
int g[N][N],used[N],size[N]; void dfs(int now)
{
dp[now][0]=0;
for(int i=1;i<=n;i++)//列舉子樹
if(g[now][i])
{
dfs(i);
size[now]+=size[i];
int r=min(p,size[now]);
for(int j=r;j>=1;j--)//列舉當前捨棄節點數
{
int r2=min(min(p,size[i]),j);
for(int k=1;k<=r2;k++)//列舉子樹狀態
dp[now][j]=min(dp[now][j],dp[now][j-k]+dp[i][k]);
}
}
dp[now][++size[now]]=1;
} int main()
{
memset(dp,0x3f,sizeof(dp));
scanf("%d%d",&n,&p);
int u,v;
p=n-p;
for(int i=1;i<n;i++)
{
scanf("%d%d",&u,&v);
g[u][v]=1;
used[v]=1;
}
for(int i=1;i<=n;i++)
if(!used[i])
{
root=i;
break;
}
dfs(root);
int ans=0x3f3f3f3f;
for(int i=1;i<=n;i++)
{
if(i==root) {ans=min(ans,dp[i][p]);continue;}
int tt=p+size[i]-n;//還要砍得
if(tt>=0) ans=min(ans,dp[i][tt]+1);
}
printf("%d\n",ans);
return 0;
}

2018.5.6