Rebuilding Roads

题意是给你一颗树你可以通过切掉数的若干条边,然后要得到一个节点数为p的子树。问你最少切掉多少条边。

首先当然是将无根树变成有根树,然后如果某个子树的大小刚好等于P的话这样最少就切一次。特殊情况是n=p,这时输出0。

其他情况就要进行dp了。 最容易想到的状态就是dp[i][j] 表示以节点i为根的子树变成j个节点需要切掉的最小边数。

然而状态转移方程不好写。因为每个节点有若干个子节点,你需要保证所有子节点的和刚好等于j,这就涉及到分配的问题,尝试的交了一次,TLE。

后来看别人代码才知道这里可以对节点i的所有子节点进行一次01 背包。 先回顾一下01 背包:

dp[i][j] = min(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);

是枚举当前的物品取或者不取的状态。 这里也是枚举当前的边删或者不删。 方程为

dp[i][x][m] = min(dp[i-1][x][m]+1,dp[i-1][x][m-k]+dp[last][son][k];

这里用3维方便理解,i表示现在在第i个子节点,last表示当前子节点对应状态的最后的值。 考虑到单调性将i去掉,得

dp[x][m] = min(dp[x][m]+1,dp[x][m-k]+dp[son][k]);

由于是树状dp 因此在算当前节点的时候,所有子节点的状态结果已经算出来。

现在的问题就是怎么初始化。

想想01背包的最初的状态为最开始什么都没装dp[0][0] = 0;

这里还没枚举第一个子节点时,也需要将根算进去,因此初始化 dp[x][1] = 0;

听说还有一种将数转化成2叉树的优化方法,有时间补上。

真是弱的不能看啊orz

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
#include<vector>
#include<algorithm>
typedef  long long LL;

#define clr(x) memset((x),0,sizeof(x))
#define inf 0x3f3f3f3f

using namespace std;
const int maxn = 161;
vector <int> v[maxn];
int dp[maxn][maxn];
int num[maxn];
int n,m;
void dfs2(int x,int fa)
{
  for(int i = 0;i<v[x].size();i++) if(v[x][i]!=fa)
  {
      dfs2(v[x][i],x);
      num[x] += num[v[x][i]];
  }
  ++num[x] ;
}
void dfs(int x,int fa)
{
  if(num[x]==1)
  {
      dp[x][1] = 0;
      return ;
  }
  dp[x][1] = 0;
  for(int i = 0;i<v[x].size();i++)if(v[x][i]!=fa){
      dfs(v[x][i],x);
      for(int j = num[x];j>=0;j--){
          int tt = min(num[v[x][i]],j);
          dp[x][j]+=1;
          for(int k = 1;k<=tt;k++)
          {
              dp[x][j] = min(dp[x][j],dp[v[x][i]][k]+dp[x][j-k]);
          }
          //printf("%d %d %d\n",x,j,dp[x][j]);
      }
  }
}

int main()
{
  scanf("%d%d",&n,&m);
  int x ,y;
  memset(dp,0x3f,sizeof dp);
  memset(num,0,sizeof num);
  for(int i = 1;i<n;i++)
  {
      scanf("%d%d",&x,&y);
      v[x].push_back(y);
      v[y].push_back(x);
    }
  int ans =inf;
  dfs2(1,0);
  dfs(1,0);
  for(int i = 2;i<=n;i++)
  {
      if(num[i]==m){
          puts("1");
          return 0;
      }
  }
  ans = dp[1][m];
  printf("%d\n",ans);
    return 0;
}