zoukankan      html  css  js  c++  java
  • hdu 3016 Man Down

    线段树 + DP

    题意:一个游戏(做题前可以先玩一下帮助理解)。题意比游戏简易:每个木板都有一个权值,可正可负或0,人在上面,自身能量要加上这个权值(即能量会发生增减)。人一开始有100能量,站在最高的木板上(要加上这个能量的权,所以其实起始能量应该为100+该板能量)。然后人往下跳,跳法有讲究并不像真实游戏那样可以移动,人下落,只能从一块木板的端点垂直下落,中途不能移动,这和实际游戏有区别,但是也使问题简化了。问这个人怎么跳,使在它落到地面的时候,能量最大,如果中途或者到地面能量<=0,或者跳到一个木板上,下面没有木板可以接住自己了,那么游戏结束,输出-1

    分析:

    既然只能垂直下落,而且是落在最近的板上,所以其实下落后处于哪个木板是唯一确定的
    (这里指的唯一确定是当从左端点下落是唯一确定,从右端点下落是唯一,所以从一个木板下落就两种可能)
    另外,这个问题本质是个DP,其实是可以直接DP的,就是从顶部走到底部的一个策略,因为从一个木板走到下面,选择是唯一
    这个DP就很容易想了,可以把每个木板看做一个点,移动看作一条有向边,那么就构成一个有向图,而且是个DAG,要使下落到地面时的能量最高
    就是在这个DAG上找一个最长路。
    但是建图是个问题,点数又太多,而且鉴于这题,没必要显式建图,只要能知道每个木板能移向哪些木板即可
    这题的线段树有什么用处(我们老是做题,知道这题被归为线段树,就死命往线段树想,其实到底为什么是线段树都不知道,就算想出来,也没意思)
    线段树就是用于解决上面的问题,快速确定每个木板可以向那些木板移动
    以当前木板的做端点X为例,从x垂直下落,满足的木板为 XL<=X<=XR ,并且是最接近当前木板的那块
    我们可以换一下思维,看做点X被覆盖了,X是被[XL,XR]覆盖(当然它除了覆盖X还覆盖了很多其他点),那么我们可以换成单点查询
    查询X的时候,发现X被覆盖了,而且是被[XL,XR]这个线段覆盖的,那么我们就返回它是哪条线段即可
    但还有个问题,点X可能不止被一个线段覆盖,而是被多个线段覆盖,那么该选哪个呢?其实这就是我们的最近问题,它下落是落在离他最近的木板上的
    所以虽然X被多个线段覆盖,我们只能要最近的那个,怎么确定最近那个呢?那就是将木板按高度升序排序
    从最矮的木板开始,让它去覆盖它对应的区间,那么从低到高,点X就一定是被最高的而且满足的木板覆盖的
    而且用当前木板去覆盖之前,先查询从它左边或者右边掉下去能到那个木板
    这样做,什么问题都解决了,可能有人会想,这样从低到高地添加木板去覆盖,可行吗,没有错误吗,答案是确定的,没有错误
    这是个值得思考的问题,而且它是个显然的问题(越是显然的又越难说清楚)

    参考了小HH,但是觉得他这题好像写得累赘了,尤其是DP那部分

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    #define N 100010
    #define lch(i) ((i)<<1)
    #define rch(i) ((i)<<1|1)
    #define max(a,b) ((a)>(b)?(a):(b))
    #define min(a,b) ((a)<(b)?(a):(b))
    
    int n;
    int dp[N];
    struct seg //木板
    {
        int h,l,r,v;
        int ln,rn; //从左端点和右端点掉下去会去到那个木板上
    }s[N];
    struct node //线段树
    {
        int mark,l,r,n;
        int mid(){
            return (l+r)>>1;
        }
    }t[4*N];
    
    int cmp_h(struct seg x ,struct seg y)
    {
        return x.h < y.h;
    }
    
    void build(int l , int r , int rt)
    {
        t[rt].l = l;   t[rt].r = r;
        t[rt].mark = 0; //没有被覆盖
        t[rt].n = -1; 
        if(l == r) return ;
        int mid = t[rt].mid();
        int ll = lch(rt);
        int rr = rch(rt);
        build(l , mid , ll);
        build(mid+1 , r , rr);
    }
    
    int query(int x ,int rt)
    {
        if(t[rt].l == t[rt].r)
            return t[rt].n;
        int mid = t[rt].mid();
        int ll = lch(rt);
        int rr = rch(rt);
        if(t[rt].mark != -1) 
        {
            t[ll].mark = t[rr].mark = t[rt].mark; //传递标记
            t[ll].n = t[rr].n = t[rt].n; //传递信息
            return t[rt].n;
        }
        if(x<=mid) return query(x , ll);
        else       return query(x , rr);
    }
    
    void updata(int l , int r ,int m , int rt)
    {
        if(t[rt].l == l && t[rt].r == r)
        {
            t[rt].mark = 1;
            t[rt].n = m;
            return ;
        }
        int mid = t[rt].mid();
        int ll = lch(rt);
        int rr = rch(rt);
        if(t[rt].mark != -1)
        {
            t[ll].mark = t[rr].mark = t[rt].mark;
            t[ll].n = t[rr].n = t[rt].n;
            t[rt].mark = -1;
        }
        if(r<=mid)     updata(l,r,m,ll);
        else if(l>mid) updata(l,r,m,rr);
        else
        {
            updata(l,mid,m,ll);
            updata(mid+1,r,m,rr);
        }
    }
    
    int main()
    {
        while(scanf("%d",&n)!=EOF)
        {
            n++; s[0].h = 0;  s[0].l = 1;  s[0].r = 100000;  s[0].v = 0;
            for(int i=1; i<n; i++)
                scanf("%d%d%d%d",&s[i].h, &s[i].l, &s[i].r, &s[i].v);
            sort(s,s+n,cmp_h); //按木板升序排序,这样就可以从下到高地添加木板
            build(1,100000,1); //这个大小,不需要离散化,减少点代码和编码复杂度
            for(int i=0; i<n; i++)
            {
                s[i].ln = query(s[i].l , 1); //查询从当前木板做端点掉下去能去哪个木板
                s[i].rn = query(s[i].r , 1); //查询从当前木板做端点掉下去能去哪个木板
                updata(s[i].l , s[i].r , i , 1);
            }
            memset(dp,0,sizeof(dp));
            dp[n-1] = 100 + s[n-1].v;
            for(int i=n-1; i>=0; i--)
            {
                int ln = s[i].ln;
                int rn = s[i].rn;
                dp[ln] = max(dp[ln] , dp[i] + s[ln].v);
                dp[rn] = max(dp[rn] , dp[i] + s[rn].v);
            }
            printf("%d\n",dp[0] = (dp[0]>0?dp[0]:-1) );
        }
        return 0;
    }
  • 相关阅读:
    PHP常见问题总结
    Java常见问题总结(二)
    C语言常见问题总结
    C#常见问题总结(三)
    C#常见问题总结(二)
    Android常见问题总结(二)
    日期和时间类函数
    Eclipse开发工具介绍
    JavaScript中逻辑运算符的使用
    多路开关模式的switch语句
  • 原文地址:https://www.cnblogs.com/scau20110726/p/3073398.html
Copyright © 2011-2022 走看看