您的位置:首页 > 其它

dp优化专辑 I - Cut the Tree [树形dp]

2011-08-19 19:44 513 查看
浙大的题还有有一定的难度的,看了大牛的解题报告才会~ 思路难想,假如想到了写代码是很快的事


题意:

对一棵树做恰好k次cut操作,每次可以cut一条边,然后丢掉其中一部分而得到一棵新的树。要求最后得到的树的最小和最大权值的和

分析:

比较经典的树形DP模型,其中每个节点对所有儿子又是一个二维DP。DP记录以该节点为根,cut了0 ~ k次所能得到的最小/最大权值。每次有两种选择,一种是保留child,这样update(dp[parent][a + b], dp[parent][a] + dp[child][b])。另一种是直接舍去这棵子树,这样update(dp[parent][a
+ b], dp[parent][a]),其中b可以取1 ~ sizeOfSubtree(j),而不仅仅是1。最后的答案包括两种情况,一种是根在最优解中,那么就是dp[root][k]。否则假设i是最优解中离根最近的点,那么最优解是dp[i][k - j],其中j可以取1 ~ (n – sizeOfSubtree(i)),同样j不仅仅可以取1。

//AC CODE:

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define INF 9999999
int dp[1010][22];//DP记录以该节点为根,cut了0到m次所能得到的最小或者最大权值
int d[1010];//d[i]表示i结点的出度
int num[1010];
int n,m;
int head[1010],cnt;
struct node//模拟vector stl里的太龟速了
{
int y,next;
} a[3030];
void add(int x,int y)
{
a[cnt].y=y;
a[cnt].next=head[x];
head[x]=cnt++;
}

void dfs(int x,int root)
{
dp[x][0]=num[x];//初始化x结点
d[x]=0;//初始化x结点
for(int i=1; i<=m; i++)//初始化x结点
dp[x][i]=INF;
for(int i=head[x]; i; i=a[i].next)
{
if(a[i].y==root)//root是i的父节点,不能当作i结点的孩子处理
continue;
dfs(a[i].y,x);
d[x]+=d[a[i].y]+1;//度数总和(出度)加上与child的这一边,所以  +1
for(int j=m; j>=0; j--)
{
//1:j刀切在父结点上,直接合并child
dp[x][j]+=dp[a[i].y][0];
//2:保持父子关系,自己切k刀,child切j-k刀
for(int k=0; k<j; k++)
dp[x][j]=min(dp[x][j],dp[x][k]+dp[a[i].y][j-k]);
//3:不要父子关系,可以切1到d[a[i].y]+1刀  父亲和孩子的边也是一刀 +1
for(int k=1; k<=d[a[i].y]+1&&k<=j; k++)
dp[x][j]=min(dp[x][j],dp[x][j-k]);
}
}
}

int cal()
{
dfs(1,0);//从结点一开始,0为1的父节点
int ans=dp[1][m];
if(m>0)
{
for(int i=2; i<=n; i++)
for(int j=max(0,m-d[1]+d[i]); j<=d[i]&&j<m; j++)//d[i]保存出度
{
ans=min(ans,dp[i][j]);
}
}
return ans;
}

int main()
{
int i,x,y;
while(scanf("%d%d",&n,&m)!=-1)
{
cnt=1;
memset(head,0,sizeof(head));
for(i=1; i<=n; i++)
scanf("%d",&num[i]);
for(i=1; i<n; i++)
{
scanf("%d%d",&x,&y);
add(x,y);//图需要建立双向边
add(y,x);
}
printf("%d ",cal());
for(i=1; i<=n; i++)
num[i]=-num[i];
printf("%d\n",-cal());
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: