zoukankan      html  css  js  c++  java
  • 洛谷 P1156 垃圾陷阱 (01背包拓展)(好题!!)

    这真是一道好题目

    学到了很多

    一开始感觉吃或者不吃会有后效性

    然后看到洛谷的题解,直接把这个有后效性的部分当作dp的维度和值

    因为这个垃圾可以堆或者不堆,所以这个很像01背包,
    但是加了非常多的限制条件,是一个升级版的01背包 


    记住思考01背包问题的时候,要思考i那一维度,最后再考虑要不要用滚动数组
    否则会增加思维难度

    这里有几个量,是高度,生命,时间
    因为时间是固定的,所以可以不理他
    然后就是高度和生命谁作维度谁做值
    生命没有具体的范围,不好枚举,
    所以我们就拿高度为维度,来枚举,范围最大为题目给的d
    f[i][j]为前i个物品高度为j的最大生命值


    则f[i][j] = max(f[i-1][j] + a[i].v)  这是吃垃圾
      f[i][j] = max(f[i-1][j-a[i].h])    这是堆垃圾
    注意前提是之前的状态是存在的,即不死。
    如果有存在状态是j >= m(题目给的高度),那么就可以输出当前垃圾的时间
    如果没有,就输出max(f[i][0])

    然后这里有个非常非常牛逼的技巧,可以简化很多
    这里的生命值是忽略时间导致生命值的减少的。(洛谷上说这是离线算法)
    但是比较的时候就要和当前时间比。
    如果这里生命值是算上时间导致的生命值减少,
    那么比较的时候显然和0比。

    那么这两者只是比较上有差别,但是如果要计算
    时间的减少的话代码会多一些。

    01背包一样,可以用滚动数组,把i这一维省掉

    然后可以用填表法或者刷表法
    其实我自己的思维习惯是填表法,即从前面
    的状态推到当前状态。

    但是对于这道题,显然刷表法更方便,即用现在的状态
    推后来的状态

    因为这里有个判断当前状态存不存在的问题
    填表法的话因为从两个状态过来,所以要判断两次
    但是如果是刷表法的话,只用判断当前的状态是否存在,
    后来的状态等到枚举后来的状态自然会判断,只用判断一次。
    大家可以自行体会一下两种方法。
     

    填表法

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #define REP(i, a, b) for(int i = (a); i < (b); i++)
    using namespace std;
    
    const int MAXN = 1123;
    int f[MAXN], n, m;
    struct node
    {
        int t, v, h;
        void read() { scanf("%d%d%d", &t, &v, &h); }
        bool operator < (const node& rhs) const
        {
            return t < rhs.t;
        }
    }a[MAXN];
    
    int main()
    {
        scanf("%d%d", &m, &n);
        REP(i, 1, n + 1) a[i].read();
        sort(a + 1, a + n + 1);
        
        f[0] = 10;
        a[0].t = 0;
        REP(i, 1, n + 1)
            for(int j = m; j >= 0; j--)
            {
                if(f[j] >= a[i].t)                    
                    f[j] = max(f[j], f[j] + a[i].v);
                if(j >= a[i].h && f[j-a[i].h] >= a[i].t) 
                    f[j] = max(f[j], f[j-a[i].h]);
                    
                if(f[j] >= a[i].t && j == m)
                {
                    printf("%d
    ", a[i].t);
                    return 0;
                }
            }
        printf("%d
    ", f[0]);
        
        return 0;
    }

    刷表法

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #define REP(i, a, b) for(int i = (a); i < (b); i++)
    using namespace std;
    
    const int MAXN = 1123;
    int f[MAXN], n, m;
    struct node
    {
        int t, v, h;
        void read() { scanf("%d%d%d", &t, &v, &h); }
        bool operator < (const node& rhs) const
        {
            return t < rhs.t;
        }
    }a[MAXN];
    
    int main()
    {
        scanf("%d%d", &m, &n);
        REP(i, 1, n + 1) a[i].read();
        sort(a + 1, a + n + 1);
        
        f[0] = 10;
        a[0].t = 0;
        REP(i, 1, n + 1)
            for(int j = m; j >= 0; j--)
            	if(f[j] >= a[i].t)
            	{
            		if(j + a[i].h >= m)
                	{
                    	printf("%d
    ", a[i].t);
                    	return 0;
               	 	}
            		f[j+a[i].h] = max(f[j+a[i].h], f[j]);
            		f[j] += a[i].v;
    			}
        printf("%d
    ", f[0]);
        
        return 0;
    }

    总结

    (1)01背包的思考方法

    (2)状态的离线设计方法

    (3)填表法与刷表法的比较

  • 相关阅读:
    KVM安装以及远程连接
    开博客祭
    CQOI 2021
    琐记——学长们
    大事祭
    关于洛谷与博客园的博客的一些声明
    CSP-S 2020 & NOIP 2020 日记与游记
    调和级数
    快速乘
    二叉堆
  • 原文地址:https://www.cnblogs.com/sugewud/p/9819403.html
Copyright © 2011-2022 走看看