题目意思:
给定一颗n(n<=5000)的树,求把多有节点分配给A,B且让叶子节点一半分给A,一半分给B 时不同分配相邻数的最小值。
分析:
直接树形背包,找一个不是叶子的节点当根节点。
d[ i ] [ j ][ k ] 代表当前在i节点,i节点分配给了k,要完成i所在子树内叶子节点分给A j个所产生的最小相邻边数。
树形背包要注意的时在背包时,先前状态总是当前只考虑前面几个子树时所产生的最小值
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<vector>
#include<map>
#include<set>
#include<stack>
#include<string>
#include<cmath>
#include<cctype>
#include<ctime>
#include<bitset>
using namespace std;
#define rep1(i,x,y) for(int i=x;i<=y;i++)
const int N = 5e3 + 10;
const int oo = 0x3f3f3f3f;
int val[N<<1],cnt_,head[N],nex[N<<1],deg[N]={0};
void init(){
cnt_ = 0;
memset(head,-1,sizeof(head));
memset(deg,0,sizeof(deg));
}
void addedge(int u,int v){
val[++cnt_] = v;
nex[cnt_] = head[u];
head[u] = cnt_;
}
int n,d[N][N][2],siz[N],tem[N][2];
void dfs(int u,int fa){
int cc = 0;
d[u][0][1]=0; //当前u分配给1,总共叶子树分配零个,的最优质
d[u][0][0]=0;
siz[u] = 0;
for(int p = head[u] ; p!=-1; p=nex[p]) if(val[p] !=fa){
int v = val[p];
dfs(v , u); cc++;
for(int j = 0; j<= siz[u] ; j++ )
for(int k=0; k < 2; k++) tem[j][k] = d[u][j][k]; //备份数组
for(int i=0;i<=siz[v];i++)
for(int j=0;j<=siz[u];j++){
if(i == 0){//当i等于0时,每个状态最少想这个子树分配零,所以求取最小值,而不需保留前面的最优值。
d[u][i+j][0] = min(d[v][i][1]+1+tem[j][0],d[v][i][0]+tem[j][0]);
d[u][i+j][1] = min(d[v][i][0]+1+tem[j][1],d[v][i][1]+tem[j][1]);
}
else {
d[u][i+j][0] = min(d[u][i+j][0],min(d[v][i][1]+1+tem[j][0],d[v][i][0]+tem[j][0]));
d[u][i+j][1] = min(d[u][i+j][1],min(d[v][i][0]+1+tem[j][1],d[v][i][1]+tem[j][1]));
}
}
siz[u]+=siz[v];
}
if(cc == 0){
siz[u] = 1;
d[u][1][0] = 0;
d[u][0][0] = oo;
}
}
int main()
{
init();
scanf("%d",&n);
int x,y;
rep1(i,1,n-1) scanf("%d %d",&x,&y),addedge(x,y),addedge(y,x),deg[x]++,deg[y]++;
int root = -1;
if(n == 2) {
printf("1\n"); return 0;
}
int all = 0;
rep1(i,1,n){
if(deg[i] != 1) root = i;
else all++;
}
memset(d,oo,sizeof(d));
dfs(root,-1);
cout<<min(d[root][all/2][0],d[root][all/2][1])<<endl;
return 0;
}