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