P1156 垃圾陷阱

发布时间 2023-11-05 16:16:47作者: 加固文明幻景

P1156 垃圾陷阱

基本思路

[受这题的影响](P2370 yyy2015c01 的 U 盘 - 加固文明幻景 - 博客园 (cnblogs.com)),我总觉得这题不应该直接把时间当作状态方程的值,于是搞了\(F[i][j]\),为前\(i\)个物品,前\(j\)时间内能到达的最大高度,然后又搞一个数组维护最优时间,但我的能力根本行不通。

#include<iostream>
#include<algorithm>
#include<cstdio>

using namespace std;

struct Trash {
	int t, h, f;
	bool operator < (const Trash& rhs) const {
		return t < rhs.t;
	}
};

const int N = 101;

int D, G;
Trash a[N];
int F[N][1010], T[N];

int main()
{
	cin >> D >> G;T[0] = 10;
	for (int i = 1; i <= G; i++)
	{
		cin >> a[i].t >> a[i].f >> a[i].h;
	}
	for (int i = 1; i <= G; i++)
	{
		T[i] = T[i - 1];
		int timeI = (a[i].t - a[i - 1].t), timeSum = T[i];
		if (T[i] - timeI <= 0)
		{
			T[i] = 10 + a[i].f;
			continue;
		}
		for (int j = 1; j <= a[G].t; j++)
		{
			F[i][j] = F[i - 1][j];
			if (j >= timeI)
			{
				if (F[i][j] < F[i - 1][j - timeI] + a[i].h)
				{
					F[i][j] = F[i - 1][j - timeI] + a[i].h;
					T[i] = timeSum - timeI;	
				} 
				else
				{
					T[i] = timeSum + a[i].f; 
				}	
			}
			if (F[i][j] >= D)
			{
				cout << T[i];
				return 0;	
			}	
		}
	} 
	cout << T[G];
	return 0;
}

显然,这个算法只能保证在无生命限制下的最优解,无法兼顾生命限制来求最优解。事实上我的\(T[i]\)如果想正常维护的话可能还得开一个动态规划,但是我觉得太复杂了,没办法只能看题解。

题解思路

首先分析题目

每个垃圾都可以用来吃或堆放”,浓浓的透露出一个背包气息。我们可以类比背包问题的放或不放。于是\(DP[i][i]\)描述为处理前\(i\)个物品的某状态\(j\),那么状态\(j\)代表什么呢?

继续分析并使用排除法

我们需要求的答案是时间,于是很自然而然的想到\(j\)描述高度或生命,而\(dp\)数组存放时间。很显然,这样状态既不完整,也写不出转移方程。而且\(dp\)数组存的是当前状态下最大或最小的价值,似乎也不满足。

这时候我们发现,有4个值可能成为状态,高度,生命,物品和时间,难道要dp三维的吗?

再分析题目,每个垃圾都有一个下落的时间,奶牛一定是在垃圾丢下来的时间就处理垃圾的(可以得出这样的最优的),那么物品就可以和时间关联起来了。这时候,我们可以把时间仅仅当作干扰量给剔除了。

需要注意的是,物品的使用顺序并不是随意的,必须按它们下落的时间顺序来先后处理。(这里排一下序即可)

那么\(j\)代表什么呢?

一下子我们并不能得出答案。先尝试\(dp[i][j]\)代表前\(i\)件物品处理后在\(j\)血量时达到的最大高度。

值得一提的是,\(j\)血量表示奶牛在暂时不考虑时间时所得到的最大血量

据说这个是叫离线

状态转移方程

\(dp[i][j] = max(dp[i- 1][j] + trash[i].h, dp[i - 1][j + trash[i].c])\)

发现这是对的,然而我们再想想,在关于\(j\)的一重循环里面,\(j\)的取值我们似乎并不好判断,甚至要枚举很大。

所以我们再尝试讨论\(dp[i][j]\)代表前\(i\)件物品处理后在\(j\)高度时达到的最大血量

状态转移方程

\(dp[i][j] = max(dp[i- 1][j] + trash[i].c, dp[i - 1][j - trash[i].h])\)

发现这样也是对的,而且\(j\)枚举起来也比较方便,于是我们选择这种算法。


实现该转移方程DP

先讨论所谓的离线算法(我也不知道是不是这样叫的)

意思就是先处理完所有的状态奶牛可以达到的血量再与时间进行讨论

这里我们使用填表法(填表法就是利用状态转移方程和上一个状态来推导出现在的状态

dp[0][0] = 10,因为一开始有\(10\)

    for(int i=1;i<=g;i++)
        for(int j=0;j<=d;j++)
        {
            if(dp[i-1][j]>=trash[i].t)
                dp[i][j]=max(dp[i][j],dp[i-1][j]+trash[i].c);
            if(j>=trash[i].h&&dp[i-1][j-trash[i].h]>=trash[i].t)
                dp[i][j]=max(dp[i][j],dp[i-1][j-trash[i].h]);
        }

需要非常非常注意的是状态的能否使用

对于以前的状态\(dp[i'][j']\)能否使用,我们的要求是\(dp[i'][j'] >= trash[i].t\)它还没死

最后扫一遍

    int maxh=0;
    int maxt=0;
    int i;
    for(i=1;i<=g;i++)
    {
        for(int j=0;j<=d;j++)
        {
            if(dp[i][j]-trash[i].t>=0)
                maxh=max(maxh,j);
            maxt=max(maxt,dp[i][j]);
        }
        if(maxh==d)
            break;
    }
    if(maxh==d)
        cout<<trash[i].t<<endl;
    else
        cout<<maxt<<endl;

这里的maxt实际只用讨论\(max(dp[i][0])\)就行了


在线的和刷表法

刷表法就是利用当前的状态,把有关联的下一状态都推出来

在线的思路是,\(dp[i][j]\)所描述的血量是当前时间(既第\(i\)件物品落下时)所真正具有的血量,而不是忽略时间。

int maxt=0;
    for(int i=0;i<g;i++)
    {
        for(int j=0;j<=d;j++)
        {
            if(dp[i][j]>=trash[i+1].t-trash[i].t)
            {
                dp[i+1][j+trash[i+1].h]=max(dp[i+1][j+trash[i+1].h],dp[i][j]-(trash[i+1].t-trash[i].t));
                dp[i+1][j]=max(dp[i+1][j],dp[i][j]+trash[i+1].c-(trash[i+1].t-trash[i].t));
            }
            if(dp[i+1][j+trash[i+1].h]>=0&&j+trash[i+1].h>=d)
            {
                cout<<trash[i+1].t<<endl;
                return 0;
            }
        }
        if(dp[i][0]!=-1)
            maxt=max(maxt,dp[i][0]+trash[i].t);
    }

在这里我们边做边判断是否上去了并更新它的最长存活时间

代码实现

#include<iostream>
#include<algorithm>
#include<cstdio>

using namespace std;

struct Trash {
	int t, h, f;
	bool operator < (const Trash& rhs) const {
		return t < rhs.t;
	}
};

const int N = 101;

int D, G;
Trash a[N];
int maxH, maxT;
int F[N][1010];

int main()
{
	cin >> D >> G;
	for (int i = 1; i <= G; i++)
	{
		cin >> a[i].t >> a[i].f >> a[i].h;
	}
	sort(a + 1, a + G + 1);
	F[0][0] = 10;
	for (int i = 1; i <= G; i++)
	{
		for (int j = 0; j <= D; j++)
		{
			if (F[i - 1][j] >= a[i].t)
			{
				F[i][j] = max(F[i][j], F[i - 1][j] + a[i].f);	
			}
			if (j >= a[i].h && F[i - 1][j - a[i].h] >= a[i].t)
			{
				F[i][j] = max(F[i][j], F[i - 1][j - a[i].h]);
			}
		}
	} 
	for (int i = 1; i <= G; i++)
	{
		for (int j = 0; j <= D; j++)
		{
			if (F[i][j] >= a[i].t)
			{
				maxH = max(maxH, j);
 			}
 			maxT = max(maxT, F[i][j]);
		}
		if (maxH == D)
		{
			cout << a[i].t;
			return 0;
		}
	}
	cout << maxT;
	return 0;
}