题解:P11325 【MX-S7-T3】「SMOI-R2」Monotonic Queue
赛时思路。
不难发现前一个区间的
考虑 dp。设
转移时模拟一个单调栈(或者说不踢队头的单调队列),每次从单调栈(队尾)踢出一个数之后就可以用区间左端点取到这里,右端点取到
注意初始化的问题:
因为转移过程是模拟一个单调栈,所以复杂度是
赛时代码加注释:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e5+5;
int a[N],c[N],s[N];
int dp[N][2];
signed main(){
ios::sync_with_stdio(0);cin.tie(0);
int n;cin>>n;
for(int i=1;i<=n;i++)cin>>c[i],s[i]=s[i-1]+c[i];
for(int i=1;i<=n;i++)cin>>a[i];
deque<int>q;//exactly a monotonic stack
for(int i=1;i<=n;i++){
dp[i][1]=-1e18;
while(!q.empty()&&a[q.back()]<a[i]){
dp[i][1]=max(dp[i][1],s[i-1]-s[q.back()-1]+max(dp[q.back()][0],dp[q.back()][1]));
//s[i-1]-s[q.back()-1]:从 i-1 踢到 q.back(),要把这里面的所有 c 加起来
//max(dp[q.back()][0],dp[q.back()][1])):左端点设到了 q.back(),这样才会踢到这里停下来;上一个右端点选择 q.back(),因为它不能小于 q.back(),而后面的点全部已经转移或者踢完了
q.pop_back();
}
if(!q.empty())dp[i][1]=max(dp[i][1],s[i-1]-s[q.back()]+max(dp[q.back()][0],dp[q.back()][1]));
if(!q.empty())dp[i][0]=s[i-1]-s[q.back()]+dp[q.back()][0];//中间踢的加起来,前面的转移过来
else dp[i][0]=s[i-1];//一直踢到开头
dp[i][1]=max(dp[i][1],dp[i][0]);//没有要求必须选最左边 ≠ 我不选最左边
q.emplace_back(i);
}
int ans=0;
for(int i=1;i<=n;i++){
ans=max(ans,dp[i][1]);
}
cout<<ans;
return 0;
}
上面是赛时的思路,而实际上本题的 dp 可以优化到不开两倍空间。
把
另外,将
a[0]=n+1;q.emplace_back(0);
int ans=0;
for(int i=1;i<=n;i++){
dp[i]=-1e18;
while(a[q.back()]<a[i]){
dp[i]=max(dp[i],s[i-1]-s[q.back()-1]+dp[q.back()]);
q.pop_back();
}
dp[i]=max(dp[i],s[i-1]-s[q.back()]+dp[q.back()]);
q.emplace_back(i);
ans=max(ans,dp[i]);
}
cout<<ans;