UVA1407 Caves

树形dp

题目大意:

一个n结点的有根树,树边权均为正数,现在有q次询问,每次询问一个距离,问这个距离从0号点出发最多能经过多少点,同一结点多次经过算1次。
n<=500,q<=1000

思路

我们考虑dp,一开始我用dp[i][j]表示i为根的子树走完j的距离后最多还能经过多少节点,不过空间不允许(too young too simple)。考虑点很少,用f[ i ][ j ]表示i为根的子树经过j个节点最小需要多少距离。但是我们考虑可能存在走到深度较深的点,之后回到根节点,再走到其他的点。我们需要加一维[0 /1 ]表示是否要回到根节点,这样逆序递推就能求出。对于每次询问只需要比较一下就好了
转移方程见代码,就是考虑了所有情况。注意枚举(更新)顺序(k从小到大枚举,j从大到小枚举)

code:

#include<iostream>
#include<cstdio>
#include<queue>
#include<algorithm>
#include<cstring>
#include<stack>
#include<cmath>
using namespace std;
const int maxn=2006;
struct hzw
{
  int to,next,v;
}e[maxn];
int head[maxn],cur,son[maxn];
inline void add(int a,int b,int c)
{
    e[cur].to=b;
    e[cur].next=head[a];
    e[cur].v=c;
    head[a]=cur++;
}
int n,f[maxn][maxn][3];
inline int findson(int s,int fa)
{
    son[s]=1;
    for (int i=head[s];i!=-1;i=e[i].next)
    {
        if (e[i].to==fa) continue;
        son[s]+=findson(e[i].to,s);
    }
    return  son[s];
}
inline void dfs(int s,int fa)
{
    f[s][1][0]=f[s][1][1]=0;
    
    for (int i=head[s];i!=-1;i=e[i].next)
    {
        if (e[i].to==fa) continue;
        dfs(e[i].to,s);
        for (int j=son[s];j>=1;--j)
        {
            for (int k=1;k<=j&&k<=son[e[i].to];++k)
            {
                f[s][j][1]=min(f[s][j][1],f[s][j-k][1]+f[e[i].to][k][1]+2*e[i].v );
                f[s][j][0]=min(f[s][j][0],f[e[i].to][k][0]+f[s][j-k][1]+e[i].v);
                f[s][j][0]=min(f[s][j][0],f[e[i].to][k][1]+f[s][j-k][0]+2*e[i].v );
            }
        }
    }
}
signed main()
{
//    freopen("violent.in","r",stdin);
    int cnt=0;
    while (1)
    {   
        int m;
        cur=0;
        memset(head,-1,sizeof(head));  
        memset(f,0x3f,sizeof(f));
        memset(son,0,sizeof(son));
        scanf("%d",&n);
        cnt++;
        if (n==0) break;
		printf("Case %d:\n",cnt);
        for (int i=1,a,b,c;i<=n-1;++i) 
        {
            scanf("%d%d%d",&a,&b,&c);
            add(a,b,c);
            add(b,a,c);
        }
        findson(0,0);
        dfs(0,0);
        scanf("%d",&m);
        for (int i=1,tmp;i<=m;++i)
        {
            scanf("%d",&tmp);
            int ans;
            for (int j=n;j>=1;--j) if (f[0][j][0]<=tmp||f[0][j][1]<=tmp) {ans=j;break;}
            printf("%d\n",ans);
        }
    }
}

收获:

1、树形dp主要就是考虑出大体的结构,利用dfs逆序(一般是)转移,注意通过数据范围考虑数组的设计。
2、dp的转移顺序十分重要,一定要手模一下。
3、如果对转移方程表示很虚,打个表看看更新情况qwq。

posted @ 2018-09-09 21:16  Splitor  阅读(130)  评论(0编辑  收藏  举报