【洛谷】P4544 [USACO10NOV]Buying Feed G (单调队列优化DP)

单调队列优化 \(DP\) 的好题。

题意

\(FJ\) 要从 \(N\) 个商店中购买 \(K\) 吨饲料。设某时刻已经买了 \(X\) 吨饲料,行驶一公里的花费为 \(X^2\) 。现在 \(FJ\)\(0\) 这个位置出发,最终要走到 \(E\) 这个位置。给出每一家商店的位置、饲料的库存以及饲料的单价,求最少花费。

思路

首先考虑朴素的状态。

\(f[i][j]\) 表示到达第 \(i\) 个商店时(还没有在第 \(i\) 个商店买饲料),已经买了 \(j\) 吨饲料的最小花费。可以得出状态转移方程:

\(f[i][j]=min(f[i-1][k]+(j-k)*c[i-1]+(x[i]-x[i-1])*j*j)\)

其中 \(c[i]\) 表示第 \(i\) 家商店的售价,\(x[i]\) 表示第 \(i\) 家商店的位置。

时间复杂度为 \(O(nk^2)\)。于是需要考虑单调队列优化。

将原来的转移方程拆开,注意到 \(((x[i]-x[i-1])*j*j+j*c[i-1])\) 为常数,可以不需要考虑。而对于剩余的 \((f[i-1][k]-k*c[i-1])\) 部分,就可以使用单调队列优化。由于本题需要求最小值,故需要维护一个单调递增的队列,若对单调队列不清楚可以看一看模板题

但是不同于简单的模板题,本题还有一个限制,也就是只有当 \(f[i-1][j]\) 这种状态合法,也就是值不为无穷大的时候,才可以考虑将它加入队列中。

最后,本题的数据范围比较大,别忘了开 long long

code:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<deque>
using namespace std;
#define int long long
const int N=521; 
const int M=10010;
const int INF=1e18;
int m,e,n,f[N][M];
struct nlc{
	int x,res,c;
	bool operator <(const nlc &t)const{
		return x<t.x;
	}
}a[N];
deque<int> q;
int min(int a,int b){return a<b?a:b;}
int max(int a,int b){return a>b?a:b;}
signed main()
{
	//freopen("233.in","r",stdin);
	scanf("%lld%lld%lld",&m,&e,&n);
	for(int i=1;i<=n;i++) scanf("%lld%lld%lld",&a[i].x,&a[i].res,&a[i].c);
	sort(a+1,a+n+1);
	for(int i=0;i<=n+1;i++)
	    for(int j=1;j<=m;j++)
	        f[i][j]=INF;
	a[n+1].x=e;
	for(int i=1;i<=n+1;i++)
	{
		q.clear();
		for(int j=0;j<=m;j++) 
		{
			while(q.size()&&j-q.front()>a[i-1].res) q.pop_front();
			if(f[i-1][j]!=INF)
			{
				while(q.size()&&f[i-1][q.back()]-q.back()*a[i-1].c>=f[i-1][j]-j*a[i-1].c) q.pop_back();
				q.push_back(j);
			}
			if(q.size()) f[i][j]=f[i-1][q.front()]+(j-q.front())*a[i-1].c+(a[i].x-a[i-1].x)*j*j;
		}  
	}
	printf("%lld\n",f[n+1][m]);
	return 0;
} 
posted @ 2021-06-04 11:28  曙诚  阅读(85)  评论(0)    收藏  举报