单调队列及其DP优化

单调队列

常应用于求一个固定滑动区间的最大值或者最小值。

滑动窗口

单调队列及其DP优化_第1张图片
单调队列的经典题目,在一个区间内,如果要求最小值,如果后面的数既比前面的数位置靠后,又比前面的数小,就说明前面的数已经失去价值了,剔除前面的数(如果一个选手比你小又比你强,那么你该退役了),用一个单调队列来维护,每次的答案就是队首。

#include 
using namespace std;
const int N = 1e6 + 5;
const int INF = 0x3f3f3f3f;
int a[N];
int q1[N];
int main(){
	int n,k;
	scanf("%d%d",&n,&k);
	for(int i = 1;i <= n;i ++)
		scanf("%d",&a[i]);
	int l = 1,r = 1;
	q1[1] = 0;
	a[0] = INF;
	for(int i = 1;i <= n;i ++){
		while(a[i] <= a[q1[r]] && r >= l) r --;//注意要先插入数再删除数,不然会出现奇奇怪怪的bug.
		q1[++ r] = i;
		while(q1[l] <= i - k) l ++;
		if(i >= k) cout << a[q1[l]] << " ";
	}
	l = 1,r = 1;
	q1[1] = 0;
	a[0] = -INF;
	cout << endl;
	for(int i = 1;i <= n;i ++){
		while(a[i] >= a[q1[r]] && r >= l) r --;
		q1[++ r] = i;
		while(q1[l] <= i - k) l ++;
		if(i >= k) cout << a[q1[l]] << " ";
	}
	return 0;
} 

单调队列DP优化

最大字序和

单调队列及其DP优化_第2张图片
求一下前缀和,对于一个点 i i i,我们要做的是求出 m a x ( s i − s j ) ( j > = i − m 且 j < = i − 1 ) max(s_i - s_j)(j>=i-m且j<=i-1) max(sisj)(j>=imj<=i1),所以我们只需要维护一个长度为m的单调队列就可以了。

#include 
using namespace std;
const int N = 300010;
const int INF = 0x3f3f3f3f;
int a[N],q[N],s[N];
int main(){
	int n,m;
	cin >> n >> m;
	for(int i = 1;i <= n;i ++)
		scanf("%d",&a[i]);
	int h = 0,t = 0;
	int ans = -INF;
	for(int i = 1;i <= n;i ++)
		s[i] = s[i - 1] + a[i];
	for(int i = 0;i <= n;i ++){//注意计算答案的时候是否要保存这个数,这四句话的顺序应该有差别
		while(q[h] < i - m && h < t) h ++;
		if(i > 0) ans = max(s[i] - s[q[h]],ans);
		while(t >= h && s[i] <= s[q[t]]) t --;
		q[++ t] = i;
	}
	printf("%d",ans);
	return 0;
}

修剪草坪

单调队列及其DP优化_第3张图片
考虑 f [ i ] f[i] f[i]为前 i i i头牛的最大方案数, f [ i ] = m a x ( f [ i − j − 1 ] + s u m [ i ] − s u m [ i − j ] ) ( j > = 1 , j < = m i n ( k , i ) . ) f[i]=max(f[i-j-1]+sum[i]-sum[i-j])(j>=1,j<=min(k,i).) f[i]=max(f[ij1]+sum[i]sum[ij])(j>=1,j<=min(k,i).)发现是一个动态过程,如果想用单调队列维护,我们发现 f [ i − j − 1 ] − s u m [ i − j ] f[i-j-1]-sum[i-j] f[ij1]sum[ij]是一个可以维护的常量,满足和后面的量无关,可以使 g [ i ] = f [ i − 1 ] − s u m [ i ] g[i]=f[i-1]-sum[i] g[i]=f[i1]sum[i],维护 g [ i ] g[i] g[i]的单调队列即可。

#include 
using namespace std;
const int N = 1e5 + 5;
typedef long long LL;
LL f[N],s[N],q[N],g[N];
int main(){
	int n,k,ans;
	cin >> n >> k;
	for(int i = 1;i <= n;i ++){
		scanf("%d",&s[i]);
		s[i] += s[i - 1];
	}
	int h = 0,t = 0;
	for(int i = 1;i <= n;i ++){
		g[i] = f[i - 1] - s[i];
		while(g[i] > g[q[t]] && t >= h) t --;
		q[++ t] = i;
		if(q[h] < i - k) h ++;
		f[i] = g[q[h]] + s[i];
	}
	cout << f[n];
	return 0;
}

旅行问题

单调队列及其DP优化_第4张图片
特别逆天的一道题目,希望未来还能有耐心再来做一遍,做起来感觉不难但是边界处理很恶心人。先讲顺序的思路,先破环成链,搞成一条线,设o为油的数量,d为i到i+1的距离, c [ i ] = o [ i ] − d [ i ] c[i] = o[i] - d[i] c[i]=o[i]d[i]那么如果 c [ 1 ] + c [ 2 ] > 0 c[1]+c[2]>0 c[1]+c[2]>0,则可以从1开到3,设 s u m [ i ] sum[i] sum[i] c [ i ] c[i] c[i]的前缀和,那么满足能从 i i i j j j的条件就是 s u m [ j − 1 ] − s u m [ i − 1 ] > = 0 sum[j-1]-sum[i-1]>=0 sum[j1]sum[i1]>=0,从 2 ∗ n 2*n 2n的位置开始往前看,处理区间内最小的 s u m [ j ] ( j > i 且 j < = i + n ) sum[j](j>i且j<=i+n) sum[j](j>ij<=i+n)的值,如果 s u m [ j ] − s u m [ i ] > = 0 sum[j]-sum[i]>=0 sum[j]sum[i]>=0则代表 i + 1 i+1 i+1的位置是合法的,逆时针就是反过来做一遍。

#include 
using namespace std;
typedef long long LL;
const int N = 1e6;
LL s[N + 5];
int o[N + 5],dist[N + 5],q[N + 5];
bool ans[N + 5];
int main(){
	int n;
	cin >> n;
	for(int i = 1;i <= n;i ++)
		scanf("%d%d",&o[i],&dist[i]);
	for(int i = 1;i <= n;i ++)
		s[i] = s[i + n] = o[i] - dist[i];
	for(int i = 1;i <= 2 * n;i ++)
		s[i] += s[i - 1];
	int hh = 0,tt = -1;
	for(int i = 2 * n;i >= 0;i --){
		if(q[hh] > i + n) hh ++;
		if(s[q[hh]] - s[i] >= 0 && i < n) ans[i + 1] = true;
		while(tt >= hh && s[i] <= s[q[tt]]) tt --;
		q[++ tt] = i;
	}
	dist[0] = dist[n];
	for(int i = 1;i <= n;i ++)
		s[i] = s[i + n] = o[i] - dist[i - 1];
	for(int i = 2 * n;i >= 1;i --)
		s[i] += s[i + 1];
	hh = 0,tt = -1;
	for(int i = 1;i <= 2 * n;i ++){
		if(q[hh] < i - n) hh ++;
		while(tt >= hh && s[i] <= s[q[tt]]) tt --;
		q[++ tt] = i;
		if(i > n && s[i + 1] <= s[q[hh]]) ans[i - n] = true;
	}
	for(int i = 1;i <= n;i ++){
		if(ans[i]) printf("TAK\n");
		else printf("NIE\n");
	}
	return 0;
}

烽火传递

单调队列及其DP优化_第5张图片
基于DP的一道题目,思维被之前的题目给局限住了,感觉一定要对某个值进行处理,但是我们可以对DP值进行处理,比如这道题目,我们设 f [ i ] f[i] f[i]为i点放狼烟的最大值,那么只要维护 f [ i − m + 1 ] f[i-m+1] f[im+1] f [ i − 1 ] f[i-1] f[i1]的最小值, f [ i ] f[i] f[i]就可以处理出来,就是之前的最小值加上当前的这个值,最后处理答案以及插入先后关系的时候要特判一下,思考要更加周全。

#include 
using namespace std;
const int N = 2 * 1e5;
int a[N + 5],f[N + 5],q[N + 5];
int main(){
	int n,m,minn = 0x3f3f3f3f;
	cin >> n >> m;
	for(int i = 1;i <= n;i ++) cin >> a[i];
	int hh = 0,tt = 0;
	for(int i = 1;i <= n;i ++){
		if(q[hh] < i - m && hh < tt) hh ++;
		f[i] = f[q[hh]] + a[i];
		while(f[i] < f[q[tt]] && tt >= hh) tt --;
		q[++ tt] = i;
	}
	for(int i = n - m + 1;i <= n;i ++)
		minn = min(f[i],minn);
	cout << minn;
	return 0;
}

绿色通道

单调队列及其DP优化_第6张图片
和上一道问题差不多,来个二分选择一下不间断长度就可以了,不多解释。

#include 
using namespace std;
const int N = 5 * 1e4;
int a[N + 5],f[N + 5],q[N + 5];
int n,t;
bool check(int x){
	int hh = 0,ff = 0;
	q[0] = 0;
	for(int i = 1;i <= n;i ++){
		f[i] = f[q[hh]] + a[i];
		if(q[hh] < i - x && hh <= ff) hh ++;
		while(f[i] <= f[q[ff]] && ff >= hh) ff --;
		q[++ ff] = i;
	}
	int minn = 0x3f3f3f3f;
	for(int i = n - x;i <= n;i ++)
		if(f[i] <= minn) minn = f[i];
	if(minn <= t) return true;
	return false;
}
int main(){
	cin >> n >> t;
	for(int i = 1;i <= n;i ++)
		scanf("%d",&a[i]);
	int l,r,ans;
	l = 0,r = n;
	while(l <= r){
		int mid = l + r >> 1;
		if(check(mid)){
			ans = mid;
			r = mid - 1;
		}else l = mid + 1;
	}
	cout << ans;
	return 0;
}

理想的正方形

单调队列及其DP优化_第7张图片
感觉单调队列优化DP的题目不算很难,这道题就是跑一个二维的单调队列优化就行,详见代码。

#include 
using namespace std;
const int N = 1e3;
int q[N + 5],q1[N + 5],tmp[N + 5][N + 5],tmp1[N + 5][N + 5][2];
int main(){
	int a,b,n;
	cin >> a >> b >> n;
	for(int i = 1;i <= a;i ++)
		for(int j = 1;j <= b;j ++)
			scanf("%d",&tmp[i][j]);
	for(int i = 1;i <= a;i ++){
		int hh = 0,tt = -1,hhh = 0,ttt = -1;
		q[hh] = 0,q1[hhh] = 0;
		for(int j = 1;j <= b;j ++){
			if(q[hh] <= j - n && hh <= tt) hh ++;
			while(tmp[i][j] <= tmp[i][q[tt]] && hh <= tt) tt --;
			q[++ tt] = j;
			tmp1[i][j][0] = tmp[i][q[hh]];
			if(q1[hhh] <= j - n && hhh <= ttt) hhh ++;
			while(tmp[i][j] >= tmp[i][q1[ttt]] && hhh <= ttt) ttt --;
			q1[++ ttt] = j;
			tmp1[i][j][1] = tmp[i][q1[hhh]];
		}
	}
	int minn = 0x3f3f3f3f;
	for(int j = n;j <= b;j ++){
		int hh = 0,tt = -1,hhh = 0,ttt = -1;
		for(int i = 1;i <= a;i ++){
			if(q[hh] <= i - n && hh <= tt) hh ++;
			while(tmp1[i][j][0] <= tmp1[q[tt]][j][0] && tt >= hh) tt --;
			q[++ tt] = i;
			if(q1[hhh] <= i - n && hhh <= ttt) hhh ++;
			while(tmp1[i][j][1] >= tmp1[q1[ttt]][j][1] && ttt >= hhh) ttt --;
			q1[++ ttt] = i;
			if(i >= n) minn = min(minn,tmp1[q1[hhh]][j][1] - tmp1[q[hh]][j][0]);
	//		cout << minn << " ";
		}
	//	cout << endl;
	}
	cout << minn;
	return 0;
}

你可能感兴趣的:(大一算法学习,算法,c++,动态规划)