题目大概是给一棵树,问最少删几条边可以出现一个包含点数为p的连通块。
任何一个连通块都是某棵根属于连通块的子树的上面一部分,所以容易想到用树形DP解决:
- dp[u][k]表示以u为根的子树中,包含根的大小k的连通块最少的删边数
- 要求答案就是min(dp[u][p],min(dp[v][p]+1)),u是整棵树的根,v是其他结点
- 转移从若干个子树各自选择要提供几个k转移,不过指数级时间复杂度,当然又是树上背包了。。
转移好烦,写得我好累好累。。还好1A了。。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 #define INF (1<<29) 6 #define MAXN 155 7 struct Edge{ 8 int u,v,next; 9 }edge[MAXN]; 10 int NE,head[MAXN]; 11 void addEdge(int u,int v){ 12 edge[NE].u=u; edge[NE].v=v; edge[NE].next=head[u]; 13 head[u]=NE++; 14 } 15 int d[MAXN][MAXN],size[MAXN],son[MAXN]; 16 void getSize(int u){ 17 size[u]=1; 18 son[u]=0; 19 for(int i=head[u]; i!=-1; i=edge[i].next){ 20 int v=edge[i].v; 21 getSize(v); 22 size[u]+=size[v]; 23 ++son[u]; 24 } 25 } 26 void dp(int u){ 27 d[u][size[u]]=0; 28 d[u][1]=0; 29 bool first=1; 30 for(int i=head[u]; i!=-1; i=edge[i].next){ 31 int v=edge[i].v; 32 dp(v); 33 ++d[u][1]; 34 if(first){ 35 for(int j=1; j<=size[v]; ++j) d[u][j+1]=d[v][j]; 36 first=0; 37 }else{ 38 for(int j=size[u]-2; j>=1; --j){ 39 ++d[u][j+1]; 40 for(int k=1; k<=min(j,size[v]); ++k){ 41 d[u][j+1]=min(d[u][j+1],d[v][k]+d[u][j+1-k]); 42 } 43 } 44 } 45 } 46 } 47 int main(){ 48 for(int i=0; i<MAXN; ++i){ 49 for(int j=0; j<MAXN; ++j) d[i][j]=INF; 50 } 51 memset(head,-1,sizeof(head)); 52 int n,p,a,b; 53 scanf("%d%d",&n,&p); 54 int deg[MAXN]={0}; 55 for(int i=1; i<n; ++i){ 56 scanf("%d%d",&a,&b); 57 addEdge(a,b); 58 ++deg[b]; 59 } 60 int root; 61 for(int i=1; i<=n; ++i){ 62 if(deg[i]==0) root=i; 63 } 64 getSize(root); 65 dp(root); 66 int res=INF; 67 for(int i=1; i<=n; ++i){ 68 if(root==i) res=min(res,d[i][p]); 69 else res=min(res,d[i][p]+1); 70 } 71 printf("%d",res); 72 return 0; 73 }