【洛谷】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;
}

浙公网安备 33010602011771号