【洛谷 P2034】选择数字(单调队列优化dp)
原创
©著作权归作者所有:来自51CTO博客作者wx62a8776062513的原创作品,请联系作者获取转载授权,否则将追究法律责任
题目链接:选择数字 - 洛谷
![【洛谷 P2034】选择数字(单调队列优化dp)_开发语言](https://s2.51cto.com/images/blog/202210/15023042_6349aad2b106626464.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184)
解题报告:
思路1:
![【洛谷 P2034】选择数字(单调队列优化dp)_c++_02](https://s2.51cto.com/images/blog/202210/15023042_6349aad2e7e8222401.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184)
参考代码:
#include<cstdio>
#include<iostream>
#include<deque>
using namespace std;
const long long Maxn=100000+20,inf=0x3f3f3f3f;
long long a[Maxn],s[Maxn],f[Maxn][2];
deque <long long> q;
long long n,k;
inline long long read()
{
long long s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0' && ch<='9')s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
return s*w;
}
void pop(long long x)
{
while(1)
{
if(q.empty())break;
if(q.front()>=x-k)break;
q.pop_front();
}
}
void push(long long x)
{
while(1)
{
if(q.empty())break;
long long i=q.back();
if(f[i][0]-s[i]>=f[x][0]-s[x] && i!=x)break;
q.pop_back();
}
q.push_back(x);
}
int main()
{
// freopen("in.txt","r",stdin);
n=read(),k=read();
for(long long i=1;i<=n;++i)
a[i]=read(),s[i]=s[i-1]+a[i];
for(long long i=1;i<=n;++i)
{
pop(i);
push(i-1);
long long j=q.front();
f[i][1]=s[i]+f[j][0]-s[j];
f[i][0]=max(f[i-1][1],f[i-1][0]);
}
printf("%lld\n",max(f[n][0],f[n][1]));
return 0;
}
思路2:(和思路1一样)
![【洛谷 P2034】选择数字(单调队列优化dp)_c++_03](https://s2.51cto.com/images/blog/202210/15023043_6349aad358eff9039.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184)
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
LL n,k,sum[100001],dp[100001];
int main(){
cin>>n>>k;
LL maxn=-INF,pos,ns;
for(int x,i=1;i<=n;++i) cin>>x,sum[i]=sum[i-1]+x;
for(int i=1;i<=k;++i){//前k个是可以直接选的(n==k时有效)
dp[i]=sum[i];
if((ns=dp[i-1]-sum[i])>=maxn) maxn=ns,pos=i;
}
for(int i=k+1;i<=n;++i){
if(pos<i-k){//当过期时
maxn=-INF;//注意所有数组都要是long long的
for(int j=pos+1;j<=i;++j){//汇编代码友好
if((ns=dp[j-1]-sum[j])>=maxn){
maxn=ns,pos=j;
}
}
} else {
if((ns=dp[i-1]-sum[i])>=maxn) maxn=ns,pos=i;//寄存器友好(不开O2)
}
dp[i]=dp[pos-1]+sum[i]-sum[pos];
}
printf("%lld",dp[n]);
return 0;
}
思路3:(最短路)
因为本题需要在每隔最多k个数字就要放弃一个,所以对于每一个数字i,向它后面的i+1~i+k+1连边,之后设一个超级起点s连向1~k,再将n-k+1~n的节点连向一个超级终点t,之后以s为起点跑最短路,所有阶段权值的总和sum减去s到t的距离就是最后的答案。
由于需要连得边数是O(nk)级别的,并且是一个点向一个区间的点连一条边,因此可以用线段树优化建图,讲连的边数变成O(nlogk),总的时间复杂度是O(nlognlogn)
思路4:(正难则反,本质还是单调队列)
![【洛谷 P2034】选择数字(单调队列优化dp)_算法_04](https://s2.51cto.com/images/blog/202210/15023043_6349aad3d6f0c67270.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184)