洛谷 P2034 选择数字 (单调队列优化dp)

题意

https://www.luogu.com.cn/problem/P2034
给出 \(n\) 个正整数 \(a_1 \sim a_n\) 。可以选一些数,但不能有超过 \(k\) 个连续的数字被选择。请求出选出的数的最大值。

题目分析

看到题面,第一眼是想到这种题:

给出 \(n\) 个正整数 \(a_1 \sim a_n\) 。可以选一些数,但不能有相邻的数字被选择。请求出选出的数的最大值。

这道题比较简单,只需dp记录每个数字拿或不拿的情况,给出代码:

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e5+5;
int n,a[N];
ll f[N][2];
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n;i++){
		f[i][0]=max(f[i-1][0],f[i-1][1]);
		f[i][1]=f[i-1][0]+a[i];
	}
	printf("%lld",max(f[n][0],f[n][1]));
	return 0;
}

但是原题要求连续 \(k\) 个数不能选,那么就需要修改状态转移方程。
之前是通过 \(i-1\) 个状态来推导第 \(i\) 个状态,但是当连续 \(k\) 个数不能选时,状态需要影响到前 \(k-1\) 个状态。
接下来开始推导,当这个数选时,那么前 \(k-1\) 个数不许有数不选,根据贪心,这个数不选后,后面全选时是最大和,那么可以推导出状态转移方程:
\(f_{i,0}=\max ~ f_{i,0},f_{i,1}\)
\(f_{i,1}=\max _{j=i-k+1}^{i-1}~ f_{j,0}+\sum_{l=j+1}^i a_l\)
区间和可以用前缀和维护,而长度相等区间最大值需要另一个数据结构维护————单调队列。
给出单调队列例题:https://www.luogu.com.cn/problem/P1886

\(l\)\(1\)\(n-l+1\) ,求 \(\max_{i=l}^{l+k-1} a_i\)

这里不展开描述,给出代码

#include <bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,k,a[N];
int q[N],l,r;
int main(){
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	l=1,r=0;
	for(int i=1;i<=n;i++){
		while(l<=r&&a[i]>a[q[l]]) l++;
		q[--l]=i;
		if(i-q[r]==k) r--;
		if(i>=k) printf("%d ",a[q[r]]);
	}
	return 0;
}

最后给出原题的代码

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e5+5;
int n,k,a[N];
int l,r,q[N];
ll s[N],f[N][2];
int main(){
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n;i++) s[i]=s[i-1]+a[i];
	l=r=1;
	for(int i=1;i<=n;i++){
		f[i][0]=max(f[i-1][0],f[i-1][1]);
		while(q[l]<i-k&&l<=r) l++;
		f[i][1]=f[q[l]][0]-s[q[l]]+s[i];
		while(f[i][0]-s[i]>f[q[r]][0]-s[q[r]]) r--;
		r++;
		q[r]=i;
	}
	printf("%lld",max(f[n][0],f[n][1]));
	return 0;
}
posted @ 2022-10-31 21:36  KevinLikesCoding  阅读(54)  评论(0编辑  收藏  举报