隔离村庄
题目描述
有 n 个村庄,通过 n-1 条双向路连通。Jyb 同学财大气粗,想要买其中的恰好 k 个村庄。为了做一些秘密的事情,他想要通过破坏原来的路,在保证自己的 k 个村庄相互连通的情况下,把这 k 个村庄与其他村庄隔离开。请问他应该买哪 k 个村庄,最少破坏多少条路
Solution
dp[i][j]表示以 i结点为子树, 制造大小为 j联通块所需要断的最少数目边数
则 dp[i][j]=min{dp[i][p−j]+dp[to][p]} 为转移方程
初值 dp[i][1]=0 表示该节点为光棍时的状态,随着子树的遍历, 该值会随着改变,即转移时注意新的子树对先前 dp值的影响, 体现在代码注释处
Code
#include<bits/stdc++.h>
#define reg register
const int maxn = 255;
int N, K, num;
int head[maxn], size[maxn], num_1[maxn], Tmp[maxn], dp[maxn][maxn];
struct Edge{ int nxt, to; } edge[maxn << 1];
void Add(int from, int to){
edge[++ num] = (Edge){ head[from], to };
head[from] = num;
}
void DFS(int k, int fa){
size[k] = 1, num_1[k] = 0;
dp[k][1] = 0;
for(reg int i = head[k]; i; i = edge[i].nxt){
int to = edge[i].to;
if(to == fa) continue ;
DFS(to, k);
size[k] += size[to];
/* for(reg int j = size[k]; j >= 1; j --) for(reg int p = 1; p <= size[to]; p ++) dp[k][j] = std::min(dp[k][j], dp[k][j-p] + dp[to][p]); */
for(reg int j = size[k]; j >= 1; j --){
dp[k][j] ++; //新的儿子对过去状态的影响
for(reg int p = 1; p < j; p ++)
dp[k][j] = std::min(dp[k][j], dp[to][j - p] + dp[k][p]);
}
}
}
int main(){
freopen("isolate.in", "r", stdin);
freopen("isolate.out", "w", stdout);
scanf("%d%d", &N, &K);
for(reg int i = 1; i < N; i ++){
int u, v;
scanf("%d%d", &u, &v);
Add(u, v), Add(v, u);
}
memset(dp, 0x3f, sizeof dp);
DFS(1, 0);
/* int Ans = 0x3f3f3f3f; for(reg int i = 1; i <= N; i ++) Ans = std::min(Ans, std::min(dp[i][N-K], dp[i][K])); */
int Ans = dp[1][K];
for(reg int i = 2; i <= N; i ++) Ans = std::min(Ans, dp[i][K] + 1);
// printf("%d\n", dp[2][5]);
printf("%d\n", Ans);
return 0;
}