CodeForces - 1077F2 单调队列优化dp

做F2之前我们先想想F1 怎么做  做了这两题可以在想题的时候更好地梳理自己的思路 以得到优化算法的方法 

因为F1数据小 可以n^3暴力 dp[i][j] 表示我们选到了第i个物品 并且选了第i个以后我们有j个物品  以下是代码

#include<bits/stdc++.h>
using namespace std;
const int N = 5e3+100;
typedef long long ll;
ll dp[N][N],a[N];
int main(){
	int n,k,x;
	scanf("%d%d%d",&n,&k,&x);
	for(int i = 1; i <= n; i++) scanf("%lld",&a[i]);
	if((n/k)>x){
		puts("-1");
		return 0;
	}
	int lim = 1;
	for(int i = 1; i <= n; i++){
		for(int j = lim; j <= min(i,x); j++){
			for(int g = max(i-k,0); g < i; g++)//可优化部分 
			dp[i][j]=max(dp[i][j],dp[g][j-1]+a[i]);
		}
		if(i%k==0)lim++;
	}	
	ll ans = 0;
	for(int i = n-k+1; i <= n; i++) ans=max(ans,dp[i][x]);
	printf("%lld\n",ans);
	return 0;
}

观察代码:前两层循环是必不可少的   关键在于优化第三层循环   是不是感觉走了很多 没用的   当我遍历到j的时候  往前找k个寻求最大的dp[g][j-1] 来转移  那我们其实只要最大的那个 其他的都没用必要 遍历  那如何优化呢  显然要利用这个单调性

一个好的方法是观察 dp表的转移  下面我们通过输出 上面代码的 dp[i][j] 来观察

for(int i = 1; i <= n; i++){
		for(int j = 1; j <= x; j++){
			if(j==x) printf("%lld\n",dp[i][j]);
			else printf("%lld ",dp[i][j]);
		}
	}

以第一个样例为例子:

 

有图中的转移关系我们可以看到   dp[4][3]由dp[2][2]和dp[3][2]转移来 但是 明显dp[3][2]大于dp[2][2]  所以dp[2][2]始终不会作为更新的跳板 (对于dp[3][2]及其后面的所有转移来说)  于是我们可以维护一个单调递减的队列  队头是最大值  如果队头到当前更新的dp[i][j]的距离大于 k 说明它已经不合适了  队头出队  然后维护单调性就行 这样n^3的复杂度就下降到了n^2

#include<bits/stdc++.h>
using namespace std;
const int N = 5e3+100;
typedef long long ll;
//http://codeforces.com/problemset/problem/1077/F2
ll dp[N][N],a[N];
ll q[N];
int main(){
	int n,k,x;
	scanf("%d%d%d",&n,&k,&x);
	for(int i = 1; i <= n; i++) scanf("%lld",&a[i]);
	if((n/k)>x){
		puts("-1");
		return 0;
	}
	memset(dp,-0x3f,sizeof(dp));
	dp[0][0]=0;
	for(int i = 1; i <= x; i++){
		int l=1,r=0;
		q[++r]=0;
		for(int j = 1; j <= n; j++){//优化后变成了n^2  在递推时画出dp表  
		//抓住递推的单调性可以极大的优化时间复杂度 
			while(l<=r&&q[l]+k<j) l++;
			dp[i][j]=dp[i-1][q[l]]+a[j];
			while(l<=r&&dp[i-1][q[r]]<=dp[i-1][j]) r--;
			q[++r]=j;
		}
	}
	ll ans = 0;
	for(int i = n-k+1; i <= n; i++) ans=max(ans,dp[x][i]);
	printf("%lld\n",ans);
	return 0;
}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值