UVA 1407 - Caves

链接

https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=4153

题解

这题做起来没那么直接
有一种大家都熟悉的思想叫做二分答案
那么考虑这样一个问题:如果二分它最后走了 mid m i d 个节点,那么问题就变成给它那么多能量,看看它能不能走过 mid m i d 个节点
假设我能构造出一种解让它恰好走过 mid m i d 个节点,那么构造的方式要么是 dp d p 要么是贪心,如果贪心,那么我也能构造出更优的解,最终回到了问题原点;所以我只考虑 dp d p ,f[i][j][0/1]表示从 i i 节点开始在子树里面走恰好j个点的最小花费,0表示不用回到根节点, 1 1 表示回到根节点,转移就是树形dp套路了,瞎写写就行,别忘了倒着枚举。
我最后只需要看看我走的这个步数需要的最小花费是不是小于等于询问即可。
这个题满足二分性质,所以可以二分,但是实际上根本没有必要,因为 Q Q 太小了,直接枚举答案即可。

思路总结

这题就难在状态设计
直觉上我应该考虑f[i][j]表示从 i i 出发还剩j能量的走的最多的节点数,但是能量太大而节点数很小,因此一个套路就似乎交换状态和表示的东西,由此就想到正解了
至于 [0/1] [ 0 / 1 ] 这一维,可以手动分析下样例,最后发现我一定会停留在一棵子树中,而且其它的子树都会回到根节点,于是这个就能想到了。

代码

//Ê÷ÐÎDP 
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cctype>
#include <cmath>
#define maxn 2018
#define int_inf 0x3f3f3f3f
#define cl(x,y) memset(x,y,sizeof(x))
using namespace std;
int f[maxn][maxn][2], head[maxn], tot, to[maxn], w[maxn], nex[maxn], root, fa[maxn], N;
int read(int x=0)
{
    char c, f=1;
    for(c=getchar();!isdigit(c);c=getchar())if(c=='-')f=-1;
    for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+c-48;
    return f*x;
}
void adde(int a, int b, int c){to[++tot]=b;w[tot]=c;nex[tot]=head[a];head[a]=tot;}
int min(int a, int b, int c){return min(min(a,b),c);}
int init()
{
    int i, a, b, c;
    tot=0;
    cl(head,0);
    cl(nex,0);
    cl(fa,0);
    cl(f,int_inf);
    N=read();
    if(!N)return false;
    for(i=1;i<N;i++)a=read()+1, b=read()+1, c=read(), fa[a]=b, adde(b,a,c);
    for(root=1;fa[root];root=fa[root]);
    return true;
}
void dp(int pos)
{
    int p, i, j;
    f[pos][1][0]=f[pos][1][1]=0;
    for(p=head[pos];p;p=nex[p])
    {
        dp(to[p]);
        for(i=N;i>1;i--)for(j=1;j<i;j++)
            f[pos][i][0]=min(f[pos][i][0],f[pos][j][1]+w[p]+f[to[p]][i-j][0],w[p]+w[p]+f[to[p]][i-j][1]+f[pos][j][0]),
            f[pos][i][1]=min(f[pos][i][1],f[pos][j][1]+w[p]+w[p]+f[to[p]][i-j][1]);
    }
}
int main()
{
    int Q, x, c=0, i;
    while(init())
    {
        printf("Case %d:\n",++c);
        dp(root);
        Q=read();
        while(Q--)
        {
            x=read();
            for(i=1;f[root][i+1][0]<=x;i++);
            printf("%d\n",i);
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值