YBTOJ 「动态规划」第6章 单调队列全解

A. 【例题1】滑动窗口

用的线段树(雾)

#include<bits/stdc++.h>
#define inl inline
#define re register
using namespace std;
const int N=1e6+10;
struct node{
	int minn,maxn;
}tree[N<<2];
int n,k,a[N];
inl void update(int k){
	tree[k].maxn=max(tree[k<<1].maxn,tree[k<<1|1].maxn);
	tree[k].minn=min(tree[k<<1].minn,tree[k<<1|1].minn);
}
inl void build(int k,int l,int r){
	if(l==r){
		tree[k].maxn=tree[k].minn=a[l];
		return ;
	}
	int mid=l+r>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
	update(k);
}
inl int query1(int k,int l,int r,int x,int y){
	if(x<=l&&r<=y) return tree[k].maxn;
	int mid=l+r>>1;
	int m=-1e9;
	if(x<=mid) m=max(m,query1(k<<1,l,mid,x,y));
	if(y>mid) m=max(m,query1(k<<1|1,mid+1,r,x,y));
	return m;
}
inl int query2(int k,int l,int r,int x,int y){
	if(x<=l&&r<=y) return tree[k].minn;
	int mid=l+r>>1;
	int m=1e9;
	if(x<=mid) m=min(m,query2(k<<1,l,mid,x,y));
	if(y>mid) m=min(m,query2(k<<1|1,mid+1,r,x,y));
	return m;
}
int main(){
	scanf("%d%d",&n,&k);
	for(re int i=1;i<=n;i++) scanf("%d",&a[i]);
	build(1,1,n);
	for(re int i=1;i<=n-k+1;i++){
		printf("%d ",query2(1,1,n,i,i+k-1));
	}
	puts("");
	for(re int i=1;i<=n-k+1;i++){
		printf("%d ",query1(1,1,n,i,i+k-1));
	}
	return 0;
}

B. 【例题2】粉刷木板

在这里插入图片描述
运用动态规划:设 d p [ i ] [ j ] dp[i][j] dp[i][j]表示第 i i i个人刷第 j j j快木板
那么不难得到初始状态 d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] ) dp[i][j]=max(dp[i-1][j],dp[i][j-1]) dp[i][j]=max(dp[i1][j],dp[i][j1])分别表示第 i i i个人不刷,与 i i i不刷当前木板
剩下的就是在区间内逐一枚举,不妨设他粉刷的区间为 [ k + 1 , j ] [k+1,j] [k+1,j],那么就可以得到 d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ k ] + p i × ( j − k ) ) dp[i][j]=max(dp[i-1][k]+p_i\times (j-k)) dp[i][j]=max(dp[i1][k]+pi×(jk))
但是这个 d p dp dp的复杂度直接T飞,于是乎我们可以将其拆分可以得到 d p [ i − 1 ] [ k ] − p i × k + p i × j dp[i-1][k]-p_i\times k+p_i\times j dp[i1][k]pi×k+pi×j
发现整个转移过程计算 d p [ i ] [ j ] dp[i][j] dp[i][j]时,只有k时在一直变化的,所以,我们可以单调队列维护 d p [ i − 1 ] [ k ] − p i × k dp[i-1][k]-p_i\times k dp[i1][k]pi×k的最大值
Code

#include<bits/stdc++.h>
#define inl inline
#define ll long long
#define re register
using namespace std;
int read(){
	int sum=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-') f=-1;c=getchar();}
	while(isdigit(c)){sum=(sum<<3)+(sum<<1)+(c^48);c=getchar();}
	return sum*f;
}
int n,m;
int dp[101][16010];
struct node{
	int l,p,s;
}a[110];
int q[20010];
inl bool cmp(node x,node y){
	return x.s<y.s;
}
int main(){
	n=read(),m=read();
	for(re int i=1;i<=m;i++){
		a[i].l=read(),a[i].p=read(),a[i].s=read();
	}
	sort(a+1,a+1+m,cmp);
	for(re int i=1;i<=m;i++){
		int l=1,r=0;
		for(re int k=max(a[i].s-a[i].l,0);k<a[i].s;k++){
			while(l<=r&&dp[i-1][q[r]]-q[r]*a[i].p<=dp[i-1][k]-k*a[i].p){
				r--;
			}
			q[++r]=k;
		}
		for(re int j=1;j<=n;j++){
			dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
			if(j>=a[i].s){
				while(l<=r&&j-q[l]>a[i].l) l++;
				if(l<=r){
					dp[i][j]=max(dp[i][j],(j-q[l])*a[i].p+dp[i-1][q[l]]);
				}
			}
		}
	}
	printf("%d",dp[m][n]);
	return 0;
}

C. 【例题3】耗费体力

在这里插入图片描述
这题就比较和谐了,首先可以很快得出 d p dp dp方程: d p [ i ] = d p [ j ] ( a j > a i ) , d p [ i ] = d p [ j ] + 1 ( a j < = a i ) dp[i]=dp[j](a_j>a_i),dp[i]=dp[j]+1(a_j<=a_i) dp[i]=dp[j](aj>ai),dp[i]=dp[j]+1(aj<=ai)
又发现只需要维护区间最大值即可,又可以用单调队列优化
Code

#include<bits/stdc++.h>
#define inl inline
#define int long long
#define re register
using namespace std;
int read(){
	int sum=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-') f=-1;c=getchar();}
	while(isdigit(c)){sum=(sum<<3)+(sum<<1)+(c^48);c=getchar();}
	return sum*f;
}
const int N=1e6+10;
int n,dp[N],m,a[N],q[N];
signed main(){
	n=read();
	for(re int i=1;i<=n;i++) a[i]=read();
	m=read();
	while(m--){
		int x=read();
		memset(dp,0,sizeof(dp));
		int l=1,r=1;q[r]=1;
		for(re int i=2;i<=n;i++){
			while(l<=r&&i-q[l]>x) l++;
			if(a[i]>=a[q[l]]) dp[i]=dp[q[l]]+1;
			else dp[i]=dp[q[l]];
			while(l<=r&&(dp[q[r]]>dp[i]||(dp[q[r]]==dp[i]&&a[q[r]]<a[i]))){r--;}
			q[++r]=i;
		}
		printf("%lld\n",dp[n]);
	}
	return 0;
} 

D. 【例题4】燃放烟火

在这里插入图片描述
可以设dp方程 d p [ i ] [ j ] dp[i][j] dp[i][j]表示第 i i i个烟花燃放时在第 j j j个位置能获得的最大幸福值
可得dp方程: d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ k ] + b i − a b s ( a i − j ) ) , ( j − ( t i − t i − 1 ) ∗ d < = k < = j + ( t i − t i − 1 ) ∗ d ) dp[i][j]=max(dp[i-1][k]+b_i-abs(a_i-j)),(j-(t_i-t_{i-1})*d<=k<=j+(t_i-t_{i-1})*d) dp[i][j]=max(dp[i1][k]+biabs(aij)),(j(titi1)d<=k<=j+(titi1)d),
发现转移之前还没进行,就MLE了,但是通过观察可以发现 d p [ i ] dp[i] dp[i]的转移只依靠 d p [ i − 1 ] dp[i-1] dp[i1]于是乎滚动数组用起来
但是发现还是会T,就在将方程拆分:得到 d p [ i − 1 ] [ k ] + ( b i − a b s ( a i − j ) ) dp[i-1][k]+(b_i-abs(a_i-j)) dp[i1][k]+(biabs(aij))发现只有 d p [ i − 1 ] [ k ] dp[i-1][k] dp[i1][k]在不断变化,于是可以想到找区间内的最小的 d p [ i − 1 ] [ k ] dp[i-1][k] dp[i1][k]
但要注意:这里的 d p [ i ] [ j ] dp[i][j] dp[i][j]的含义就变了,我们为了简化题意, a n s = ∑ b i − m i n ( d p [ m ] [ j ] ) ans=\sum b_i-min(dp[m][j]) ans=bimin(dp[m][j])
d p [ i ] [ j ] dp[i][j] dp[i][j]的含义就转变为了 1 − i 1-i 1i次的 a b s ( a i − j ) abs(a_i-j) abs(aij)和的最小值,运用两次单调队列找到区间内的上一个位置的最小值即可
Code

#include<bits/stdc++.h>
#define inl inline
#define int long long
#define re register
using namespace std;
int read(){
	int sum=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-') f=-1;c=getchar();}
	while(isdigit(c)){sum=(sum<<3)+(sum<<1)+(c^48);c=getchar();}
	return sum*f;
}
int n,m,d;
int dp[2][150010],p=0,sum,t[2];
int q[150010];
signed main(){
	n=read(),m=read(),d=read();
	for(re int i=1;i<=m;i++){
		int a=read(),b=read();t[p^1]=read();
		sum+=b;
		int len=d*(t[p^1]-t[p]);
		int l=1,r=0;
		for(re int j=1;j<=n;j++){
			while(l<=r&&q[l]<j-len) l++;
			while(l<=r&&dp[p][q[r]]>dp[p][j]) r--;
			q[++r]=j;
			dp[p^1][j]=dp[p][q[l]]+abs(a-j);
		}
		l=1,r=0;
		for(re int j=n;j>=1;j--){
			while(l<=r&&q[l]>j+len) l++;
			while(l<=r&&dp[p][q[r]]>dp[p][j]) r--;
			q[++r]=j;
			dp[p^1][j]=min(dp[p^1][j],dp[p][q[l]]+abs(a-j));
		}
		p^=1;
	}
	int tmp=1e18;
	for(re int i=1;i<=n;i++){
		tmp=min(tmp,dp[p][i]);
	}	
	printf("%lld",sum-tmp);
	return 0;
}

E. 【例题5】跳房子

在这里插入图片描述
首先时很明显的二分,设 d p [ i ] dp[i] dp[i]为跳到第 i i i个格子的最大的分
很明显的单调队列维护:维护符合范围的dp最大值,直接用 d p [ i ] = m a x d p [ j ] + a [ i ] dp[i]=max{dp[j]}+a[i] dp[i]=maxdp[j]+a[i]来转移
Code

#include<bits/stdc++.h>
#define inl inline
#define int long long
#define re register
using namespace std;
int read(){
	int sum=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-') f=-1;c=getchar();}
	while(isdigit(c)){sum=(sum<<3)+(sum<<1)+(c^48);c=getchar();}
	return sum*f;
}
const int N=5e5+10;
int n,d,k;
int a[N],b[N];
int now,dp[N];
inl bool check(int x){
	memset(dp,0,sizeof(dp));
	now=0;
	deque<int> q;
	int lg=max(1ll,d-x),rg=d+x;
	for(re int i=1;i<=n;i++){
		while(a[now]+lg<=a[i]){
			while(!q.empty()&&dp[q.back()]<dp[now]) q.pop_back();
			q.push_back(now);
			now++;
		}
		while(!q.empty()&&a[q.front()]+rg<a[i]) q.pop_front();
		if(!q.empty()) dp[i]=dp[q.front()]+b[i];
		else dp[i]=-1e18;
		if(dp[i]>=k) return 1;
	}
	return 0;
}
signed main(){
	n=read(),d=read(),k=read();
	for(re int i=1;i<=n;i++){
		a[i]=read(),b[i]=read();
	}
	int l=0,r=a[n];
	while(l<r){
		int mid=(l+r)>>1;
		if(check(mid)) r=mid;
		else l=mid+1;
	}
	printf("%lld",l==a[n]?-1:l);
	return 0;
}

F. 1.最大矩阵

link
其实就是一个路径压缩
Code

#include<bits/stdc++.h>
#define inl inline

#define int long long

#define re register

using namespace std;
int read(){
	int sum=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-') f=-1;c=getchar();}
	while(isdigit(c)){sum=(sum<<3)+(sum<<1)+(c^48);c=getchar();}
	return sum*f;
}
const int N=1e5+10;
int n,h[N],last[N],nxt[N];
signed main(){
	n=read();
	for(re int i=1;i<=n;i++) h[i]=read();
	last[1]=0;
	for(re int i=2;i<=n;i++){
		int x=i-1;
		while(x&&h[x]>=h[i]) x=last[x];
		last[i]=x;
	}
	nxt[n]=n+1;
	for(re int i=n-1;i>=1;i--){
		int x=i+1;
		while(x<=n&&h[x]>=h[i]) x=nxt[x];
		nxt[i]=x;
	}
	int ans=0;
	for(re int i=1;i<=n;i++){
		ans=max(ans,(nxt[i]-last[i]-1)*h[i]);
	}
	printf("%lld",ans);
	return 0;
}

G. 2.最大指数和

在这里插入图片描述
发现可以用线段树来维护 [ i − R   i − L ] [i-R~i-L] [iR iL]的最大值,最后路径输出时直接暴力dfs即可
Code

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ls l,mid,rt<<1

#define rs mid+1,r,rt<<1|1

using namespace std;
const int N=200005;
int tree[N<<2],n,l,r,f[N<<1],a[N<<1];
void pushup(int rt)
{
    tree[rt]=max(tree[rt<<1],tree[rt<<1|1]);
} 
void build(int k,int num,int l,int r,int rt)
{
    if(l==r&&l==k)
    {
        tree[rt]=num;
        return ;
    }
    int mid=(l+r)>>1;
    if(k<=mid) build(k,num,ls);
    else build(k,num,rs);
    pushup(rt);
}
void Init()
{
    memset(f,0,sizeof(f));
    memset(tree,-1e9,sizeof(tree));
    memset(a,0,sizeof(a));
    scanf("%d%d%d",&n,&l,&r);
    for(int i=0;i<=n;i++)
    {
        scanf("%d",&a[i]);
    }
}
int query(int le,int ri,int l,int r,int rt)
{
    if(le<=l&&ri>=r)
    {
        return tree[rt];
    }
    int mid=(l+r)>>1;
    if(ri<=mid)
    {
        return query(le,ri,ls);
    }
    if(le>mid)
    {
        return query(le,ri,rs);
    }
    if(le<=mid&&ri>mid)
    {
        return max(query(le,mid,ls),query(mid+1,ri,rs));
    }
}
void dfs(int k)
{

    if(k==0)
    {
        printf("0 ");
        return ;
    }
    for(int i=max(k-r,0);i<=k-l;i++)
    {
        if(f[i]+a[k]==f[k])
        {
            int t1=a[i],t2=f[i];
            dfs(i);

            break;
        }
    }
    if(k>=n)
    {
        printf("-1");
        return ;
    }
    printf("%d ",k);
    return ;
}
void work()
{
    f[0]=a[0];
    build(0,f[0],0,n+r,1);
    for(int i=l;i<=n+r;i++)
    {
        f[i]=query(max(0,i-r),i-l,0,n+r,1)+a[i];
        build(i,f[i],0,n+r,1);
    }
    int maxn=0,ord;
    for(int i=n;i<=n+r;i++)
    {
        if(f[i]>maxn)
        {
            maxn=f[i];
            ord=i;
        }
    }
    printf("%d\n",maxn);
    dfs(ord);
}
int main()
{

    Init();
    work();
    return 0;
}

H. 3.最小间隔

在这里插入图片描述
还是二分,单调队列维护的是区间最小值,最后判断的时候要从n-x开始
Code

#include<bits/stdc++.h>
#define ll long long
#define re register
#define inl inline
using namespace std;
int read(){
	int sum=0,f=1;char c=getchar();
	while(!isdigit(c)) {if(c=='-') f=-1;c=getchar();}
	while(isdigit(c)){sum=(sum<<3)+(sum<<1)+(c^48);c=getchar();}
	return sum*f;
}
const int N=5e4+10;
int n,a[N],q[N],t;
int dp[N];
bool check(int x){
	int L=1,R=1;
	memset(dp,0,sizeof(dp));
	for(re int i=1;i<=n;i++){
		while(L<=R&&q[L]+x+1<i) L++;
		dp[i]=dp[q[L]]+a[i];
		while(L<=R&&dp[q[R]]>=dp[i]) R--;
		q[++R]=i;
	}
	for(re int i=n-x;i<=n;i++){
		if(dp[i]<=t) return true;
	}
	return false;
}
int main(){
	n=read(),t=read();
	for(re int i=1;i<=n;i++) a[i]=read();
	int l=0,r=n;
	while(l<r){
		int mid=(l+r)>>1;
		if(check(mid)) r=mid;
		else l=mid+1;
	}
	printf("%d\n",r);
	return 0;
}

I. 4.写博客

在这里插入图片描述
思路 每一个子串用KMP跑一遍,记录一下每一个位置之前的最近的违禁单词起始位置
设dpi为第i个位置必须删掉,能得到的最小减少量,那么答案就是dp[n+1]
那么用单调队列维护维护的范围也有了,那么就直接开干就行了!

#include<bits/stdc++.h>
#define ll long long
#define re register
#define inl inline
using namespace std;
int read(){
	int sum=0,f=1;char c=getchar();
	while(!isdigit(c)) {if(c=='-') f=-1;c=getchar();}
	while(isdigit(c)){sum=(sum<<3)+(sum<<1)+(c^48);c=getchar();}
	return sum*f;
}
const int N=2e5+10;
int n,m,nxt[N],a[N],r[N],dp[N],q[N],l,rr;
char s[N],t[N];
int main(){
	n=read(),m=read();
	scanf("%s",s+1);
	for(re int i=1;i<=n;i++) a[i]=read();
	while(m--){
		scanf("%s",t+1);
		int len=strlen(t+1),j=0;
		for(re int i=2;i<=len;i++){
			while(j&&t[j+1]!=t[i]) j=nxt[j];
			if(t[j+1]==t[i]) j++;
			nxt[i]=j; 
		}
		j=0;
		for(re int i=1;i<=n;i++){
			while(j&&t[j+1]!=s[i]) j=nxt[j];
			if(t[j+1]==s[i]) j++;
			if(j==len){
				r[i]=max(r[i],i-len+1);
			}
		}
	}	
	for(re int i=1;i<=n+1;i++) r[i]=max(r[i],r[i-1]);
	l=1,rr=1;
	for(re int i=1;i<=n+1;i++){
		while(l<=rr&&q[l]<r[i-1]) l++;
		dp[i]=dp[q[l]]+a[i];
		while(l<=rr&&dp[q[rr]]>=dp[i]) rr--;
		q[++rr]=i;
	}
	printf("%lld\n",dp[n+1]);
	return 0;
}

J. 5.出题方案

在这里插入图片描述
首先用前缀和来维护区间
用dp[i],来维护[1-n]的最小值
在这里插入图片描述

在这里插入图片描述

#include<bits/stdc++.h>
#define int long long
#define re register
#define inl inline
using namespace std;
int read(){
	int sum=0,f=1;char c=getchar();
	while(!isdigit(c)) {if(c=='-') f=-1;c=getchar();}
	while(isdigit(c)){sum=(sum<<3)+(sum<<1)+(c^48);c=getchar();}
	return sum*f;
}
const int N=3e5+10;
template <typename T> struct que{
	T a[N],st=1,ed=0;
	void dque(){st=1,ed=0;}
	inl void clear(){st=1,ed=0;}
	inl int size(){return ed-st+1;}
	inl bool empty(){return !(ed-st+1);}
	inl void pop_front(){st++;}
	inl void pop_back(){ed--;}
	inl T front(){return a[st];}
	inl T back(){return a[ed];}
	inl void push_back(T x){a[++ed]=x;}
	inl T operator [] (int x){return a[st+x-1];}
};
int n,m;
int dp[N],a[N],sum[N];
inl int calc(int l,int r){
	return l>r ? -1e18 : sum[r]-sum[l-1];
}
multiset<int> s;
que<int> q;
signed main(){
	memset(dp,0x3f3f3f3f,sizeof(dp));
	n=read(),m=read();
	for(re int i=1;i<=n;i++){
		a[i]=read();
		sum[i]=sum[i-1]+a[i];
	}
	dp[0]=0;
	int j=0;
	q.push_back(0);
	for(re int i=1;i<=n;i++){
		while(calc(j+1,i)>m) j++;
		while(!q.empty()&&q.front()<j){
			if(q.size()>=2) s.erase(s.find(dp[q[1]]+a[q[2]]));
			q.pop_front(); 
		}
		while(!q.empty()&&a[q.back()]<a[i]){
			if(q.size()>=2) s.erase(s.find(dp[q[q.size()-1]]+a[q[q.size()]]));
			q.pop_back();
		}
		if(!q.empty()) s.insert(dp[q.back()]+a[i]);
		q.push_back(i);
		dp[i]=dp[j]+a[q.front()];
		if(q.size()>=2) dp[i]=min(dp[i],*s.begin());
	}
	printf("%lld",dp[n]);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值