题意:有n个零件,每个零件重量不同,你需要把它们运输回去,你只能运输连续的一段零件,且这连续的一段零件重量之和不能大于limit,而运输某一段的花费是这段重量的最大值。
题解:
这毫无疑问是一道毒瘤题,dp方程很简单,优化却比较复杂。
我们令dp[i]表示前i个零件的最小费用
所以dp[i]=min(dp[j]+max_w(j+1,i)) (sumw[i]-sumw[j]<=limit)
朴素做法O(n^2),会超时,接下来是优化。
对于dp[i],能更新它的j的max_w值在j+1到i这个区间一定是单调不上升的
而且对于j+1到i的某一段区间来说,这个值都是一定的。再来看dp[j],它
也具有类似的单调性,从1到n,dp的值都一定是单调不下降的。
那么我们要求的是min(dp[j-1]+max_w(j,i)),所以对于一段max_w相同的
区间,我们只需取其最左边的dp[j]即可,若再往右取一点点,dp单调不下降
一定不会更优。
分析到这里,我们就可以着手优化了,我们可以使用一个单调队列来记录max_w的
值,当然,根据上面的分析,我们只需要记录max_w相同区间内位置在最左边的那
一个下标,在i更新的时候,可能会出现一个新的max_w,这时,就将单调队列更新
然后对于快速地求出min(dp[j]+max_w(j+1,i)),这个可以使用一个set,来支持插入,
删除,查询三个操作,当然,自己敲一个合适的数据结构也是可以的,当单调队列中
的值需要被更新的时候,set也需要被更新了
虽然理完了思路,但是我仍然对如何实现这个代码感到迷茫,于是对着标程边打边理解
在下面给出的代码中将有我理解时所加上的一些注释,有一些是废话,但有一些是有用的
code:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<set>
typedef long long LL;
const int MAXN=300005;
using namespace std;
int n,Q[MAXN]; LL sum[MAXN];
LL limit,w[MAXN],dp[MAXN];
struct ISET{
multiset<LL> S;
void Insert(int v){S.insert(v);}
void Delete(int v){S.erase(S.lower_bound(v));}
int top(){return *S.begin();}
}iset;
inline void Read(LL &Ret){
char ch;bool flag=0;
for(;ch=getchar(),ch<'0'||ch>'9';)if(ch=='-')flag=1;
for(Ret=ch-'0';ch=getchar(),'0'<=ch&&ch<='9';Ret=Ret*10+ch-'0');
flag&&(Ret=-Ret);
}
int main()
{
scanf("%d",&n); Read(limit);
for(int i=1;i<=n;i++)
Read(w[i]),sum[i]=sum[i-1]+w[i];
int h=1,t=0,left=1;
Q[++t]=1; iset.Insert(dp[1]=w[1]);//定义初始状态,并在set和队列中插入这个状态
//Q中储存的是max_w相同的一段区间内的最右边的那个值,这和理论推导出来的似乎恰好相反,但事实上,这样做可以简化代码,因为
//状态转移是dp[j-1],这不就是Q的第二个元素吗
for(int i=2;i<=n;i++)
{
while(sum[i]-sum[left-1]>limit&&left<i)//去掉非法情况
{
iset.Delete(dp[left-1]+w[Q[h]]);//此时这一段的重量已超过limit,删除掉最左边的情况
if(Q[h]==left) left++, h++;//发现队列中也存在这一非法情况,删除队列中的这一情况
else left++,iset.Insert(dp[left-1]+w[Q[h]]);//Q[h]是这段max_w相同区间的最右边的下标,不是最左边的,如果这里
//是最左边的话,在Q[h]==left时h++后可能是非法的
//这句话的大部分操作都会被iset.Delete(dp[left-1]+w[Q[h]]);给抵消掉,他的作用其实只是将第1个合法的值给插入到set中
}//注意这样一个细节,是否会存在left在初始时就比Q[h]大的情况,导致插入了许多max_w相同的点,其实并不会,注意到left只
//会在这里更新,初始时与Q[h]相同的,而Q[h]就算被更新,也只会往大了更新
while(h<=t&&w[i]>=w[Q[t]])//更新单调队列,因为是更新的最右边的,w[i]==w[Q[t]]时也要更新
{
if(h==t) iset.Delete(dp[left-1]+w[Q[t]]);
else iset.Delete(dp[Q[t-1]]+w[Q[t]]);
t--;
}
Q[++t]=i;
if(h==t) iset.Insert(dp[left-1]+w[i]);
else iset.Insert(dp[Q[t-1]]+w[i]);
dp[i]=iset.top();
}
printf("%lld",dp[n]);
return 0;
}//其实这个代码最令人难以理解的地方就是Q 中存储的是一段max_w相同区间内的最右边的下标,这一点卡了我很久