题目链接: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 )
其中v是x的子树,val[v][x]表示v到x的那条边的费用。这个转移方程相当于在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;
}
}
}