HDU 3672 Caves ACM/ICPC 2007 成都区域赛 C 背包+树形DP

题目链接:http://acm.split.hdu.edu.cn/showproblem.php?pid=3672


该题的解法是背包型的树DP


题目大意:给出一颗以0为根的树,给出最多1000个x,求用价值x能够从根开始最多到达多少个节点。


节点数n是小于500的,于是能够考虑预处理出结果,然后询问用二分查找实现logT,这样总的复杂度就是O(O(预处理)*logT)


我们设f[i][j][0]表示考虑i为根的这颗子树,达到j个节点并且返回i所需的最小价值;f[i][j][1]表示考虑i为根的这颗子树,达到j个节点但是不用返回i所需的最小价值。

那么二分查找到的f[0][j][1]<= x <f[0][j+1][1]中的j就是题目要求的答案。

边界条件是在叶子节点中f[i][1][0]=f[i][1][1]=0,非叶子节点也是,并且初始其他的f都为INF

状态转移方程:

f[x][j+k][1]=Min(  f[x][j+k][1]  ,  f[v][j][1]+f[x][k][1]+val[v][x]*2  )

其中vx的子树,val[v][x]表示vx的那条边的费用。这个转移方程相当于在v点之前的子树中取k个点,v点所在的子树取j个点,就是一个背包的策略。

 

f[x][j+k][0]=Min(  f[x][j+k][0]  ,  f[v][j][1]+f[x][k][0]+val[v][x]*2  ,f[v][j][0]+f[x][k][1]+val[v][x] )

前面那个是在v点之前的子树中走下去并且不回来,然后在v所在的子树是走下去再回来,这种情况下val[v][x]要计算两次;后面的是在v点之前的子树中走下去并且回到x,然后在v点的子树中走下去不返回x,所以val[v][x]只用计算一次。

 

有一点要注意的是,在计算的时候,f的值不能实时更新。因为如果更新,更新之后的结果会用到后面的计算里面,答案就会出错。那么就需要在计算每一个v的时候,临时记录以下f[0]f[1],最后算完v之后再把f更新。

 

由于f[i][j][0]中的i和j都是最大为n,那么状态最多有n^2个,状态的转移最大是n^2的,也就是说DP的总复杂度最坏是n^4的。但是实际上远远不会到n^4这样的复杂度,以及时限给的5000ms,这样就能够过了。


二分查找最终的结果还是很简单的。


PS:我的实现中f[0]和f[1]我是分别用f和g两个数组来表示的。

 

AC代码:


#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <vector>
#include <queue>
#include <cstring>
#define MAXN 600
#define Min(a,b) (a<b?a:b)
#define INF 0x3f3f3f3f
#define ll long long
using namespace std;


//f g从1开始 
struct node
{
	int father;
	vector<int> son;
	ll val;
	ll f[MAXN];
	ll g[MAXN];
	int sz;
} Tree[MAXN];

void Clear(int x)
{
	Tree[x].father=-1;
	Tree[x].son.clear();
	Tree[x].val=-1;
	Tree[x].sz=1;
	memset(Tree[x].f,INF,sizeof(Tree[x].f));
	memset(Tree[x].g,INF,sizeof(Tree[x].g));
}

vector<int> G[MAXN][2];

int addEdge(int a,int b,int c)
{
	G[a][0].push_back(b);
	G[b][0].push_back(a);
	G[a][1].push_back(c);
	G[b][1].push_back(c);
}

int n;

void Trans()
{
	int a,v,i,m;
	bool vis[MAXN];
	memset(vis,0,sizeof(vis));
	queue<int> u;
	vis[0]=1;
	u.push(0);
	while(!u.empty())
	{
		a=u.front();
		u.pop();
		m=G[a][0].size();
		for(i=0;i<m;i++)
		{
			v=G[a][0][i];
			if(vis[v])
			{
				Tree[a].father=v;
				Tree[a].val=G[a][1][i];
			}
			else
			{
				Tree[a].son.push_back(v);
				vis[v]=1;
				u.push(v);
			}
		}
		G[a][0].clear();
		G[a][1].clear();
	}
}

void DP(int x)
{
	int i,j,m,v,k;
	int mf,ms;
	ll nf[MAXN],ng[MAXN];
	
	m=Tree[x].son.size();
	Tree[x].f[1]=0;
	Tree[x].g[1]=0;
	if(m==0) return;
	
	for(i=0;i<m;i++)
	{
		v=Tree[x].son[i];
		DP(v);
		
		mf=Tree[x].sz;
		ms=Tree[v].sz;
		memset(nf,INF,sizeof(nf));
		memset(ng,INF,sizeof(ng));
		
		for(j=1;j<=mf;j++)
		{
			for(k=1;k<=ms;k++)
			{
				nf[j+k]=Min(nf[j+k],Tree[x].f[j]+Tree[v].f[k]+Tree[v].val*2);
				ng[j+k]=Min(ng[j+k],Tree[x].f[j]+Tree[v].g[k]+Tree[v].val);
				ng[j+k]=Min(ng[j+k],Tree[x].g[j]+Tree[v].f[k]+Tree[v].val*2);
				if(nf[j+k]>5500000) nf[j+k]=INF;
				if(ng[j+k]>5500000) ng[j+k]=INF;
			}
		}
		Tree[x].sz=mf+ms;
		for(j=1;j<=mf+ms;j++)
		{
			Tree[x].f[j]=Min(Tree[x].f[j],nf[j]);
			Tree[x].g[j]=Min(Tree[x].g[j],ng[j]);
		}
	}
}

int Search(ll x,int l,int r,ll* f) //======
{
	int mid;
	while(l!=r)
	{
		if(r-l==1)
		{
			if(x>=f[r]) l=r;
			else r=l;
			break;
		}
		mid=(l+r)/2;
		if(f[mid]<=x) l=mid;
		else r=mid-1;
	}
	return l;
}

int main()
{
	
	int m,size;
	ll a,b,c;
	int Case=0,i,j;
	while(cin>>n,n)
	{
		Case++;
		for(i=0;i<n;i++) Clear(i);
		for(i=0;i<n-1;i++)
		{
			scanf("%d%d%d",&a,&b,&c);
			addEdge(a,b,c);
		}
		Trans();
		DP(0);
		
		cin>>m;
		size=Tree[0].sz;

		
		cout<<"Case "<<Case<<":"<<endl;
		
		for(i=0;i<m;i++)
		{
			scanf("%lld",&a);
			cout<<Search(a,1,size,Tree[0].g)<<endl;
		}
		
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值