9.DP单调队列优化

先弄出朴素DP->在用单调队列优化

一般都是区间的最大最小值,而且滑动的,才用单调队列优化

目录

模板 滑动窗口(单调队列)

1.最大子序和

2.旅行问题

3.烽火传递

4.绿色通道(二分)

5.修剪草坪

6.理想的正方形


模板 滑动窗口(单调队列)

信息学奥赛一本通(C++版)在线评测系统 (ssoier.cn)

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int a[N],q[N];//a存的是元素,q存的是队列,每个数的位置
int main()
{
    int n,k;
   scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    int hh=0,tt=-1;//由于刚开始队列为空,所以没元素
    //求滑动窗口最小值
    for(int i=1;i<=n;i++)//枚举一遍所有数
    {
        if(hh<=tt&&i-q[hh]>k-1) hh++;//假如滑出窗口了,则把队头弹出
        while(hh<=tt&&a[q[tt]]>=a[i]) tt--;//假如队尾的数小于当前数,又因为要最小值,所以弹出队尾
        q[++tt]=i;//把当前数放进队列里头
        if(i>=k)//假如符合窗口条件
         printf("%d ",a[q[hh]]);//输出最小的数
    }
    puts("");
     //求滑动窗口最大值,跟最小值一样,除了队尾弹出的是小于当前的数
    hh=0,tt=-1;
      for(int i=1;i<=n;i++)
    {
        if(hh<=tt&&i-q[hh]>k-1) hh++;
        while(hh<=tt&&a[q[tt]]<=a[i]) tt--;//因为要最大值,假如队尾小于当前数,则弹出
        q[++tt]=i;
        if(i>=k)
         printf("%d ",a[q[hh]]);
    }
   return 0;
}

 

1.最大子序和

135. 最大子序和 - AcWing题库

#include<bits/stdc++.h>
using namespace std;
const int N=3e5+10;
int s[N],q[N];//a存的是前缀和,q存的是队列,每个数的位置
int main()
{
    int n,k,ans=-0x3f3f3f3f;
   scanf("%d%d",&n,&k);
   for(int i=1;i<=n;i++)
   {
       scanf("%d",&s[i]);
       s[i]+=s[i-1];//求前缀和
   }
   //由于s[0]也是算前缀和,所以队列刚开始有一个元素q[0]
   int hh=0,tt=0;
   //求最小值
   for(int i=1;i<=n;i++)
   {
       if(i-q[hh]>k) hh++;//假如滑出窗口,则队头弹出
       ans=max(ans,s[i]-s[q[hh]]);//更新一下以这个结尾的子序列最大和
       while(hh<=tt&&s[q[tt]]>=s[i]) tt--;//因为要最小值,假如队尾大于当前数,则弹出
       q[++tt]=i;//再把当前数放进队列中
   }
   printf("%d\n",ans);
   return 0;
}

 

2.旅行问题

信息学奥赛一本通(C++版)在线评测系统 (ssoier.cn)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e6+10;
ll s[N];//s存前缀和
int q[N];//q是队列,存位置
int o[N],d[N];//o存当前油量,d存到下一个点的距离
bool ans[N];//用来标记每个数能不能到达n个点
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d%d",&o[i],&d[i]);
    //顺时针做
    for(int i=1;i<=n;i++) s[i]=s[i+n]=o[i]-d[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;i--)
    {
        if(hh<=tt&&q[hh]-i>=n) hh++;//假如滑出窗口,则弹出队头
        while(hh<=tt&&s[q[tt]]>=s[i]) tt--;//因为要最小值,则大于当前数的队尾都弹出
        q[++tt]=i;//把当前数放进队尾中
        //下面前缀和得减前一个数,才能得到当前区间的油量
        if(i<=n&&s[q[hh]]-s[i-1]>=0) ans[i]=true;//假如窗口大于n,也即合法的情况下,并且油量不会低于0,则标记这个点可以经过n个点
    }
    //逆时针做
    d[0]=d[n];
    for(int i=1;i<=n;i++) s[i]=s[i+n]=o[i]-d[i-1];//处理油量
    for(int i=1;i<=2*n;i++) s[i]+=s[i-1];//求一次前缀和
    hh=0,tt=-1;//由于是环刚开始没有元素
    for(int i=1;i<=2*n;i++)
    {
        if(hh<=tt&&i-q[hh]>n) hh++;//假如滑出窗口,则弹出队头,记住这里得对一个元素因为前缀和减的话减的是前一个数
        //下面前缀和得减前一个数,但是前面我们已经多了一个数,所以之间减即可
        if(i>n&&s[i]-s[q[hh]]>=0) ans[i-n]=true;//假如窗口大于n,也即合法的情况下,并且油量不会低于0,则标记这个点可以经过n个点
        while(hh<=tt&&s[q[tt]]<=s[i]) tt--;//因为要最大值,则小于当前数的队尾都弹出
        q[++tt]=i;//把当前数放进队尾中
    }
    for(int i=1;i<=n;i++)//最后看看那个点可以去到n个点
        if(ans[i]) puts("TAK");
        else puts("NIE");
   return 0;
}

 

3.烽火传递

信息学奥赛一本通(C++版)在线评测系统 (ssoier.cn)

 

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int a[N],f[N],q[N];
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>a[i];
    int hh=0,tt=0;//f[0]意思为点燃前0个烽火台的价值是0,所以队列刚开始有一个元素0
    for(int i=1;i<=n;i++)
    {
        if(i-q[hh]>m) hh++;//假如滑出了窗口,则弹出队头
        f[i]=f[q[hh]]+a[i];//状态计算
        while(hh<=tt&&f[q[tt]]>=f[i]) tt--;//因为要最小,则队尾大于当前数的都弹出
        q[++tt]=i;//把当前数入队
    }
    int ans=0x3f3f3f3f;
    for(int i=n-m+1;i<=n;i++) ans=min(ans,f[i]);//最后求一次m区间的最小值
    cout<<ans<<endl;
   return 0;
}

 

4.绿色通道(二分)

信息学奥赛一本通(C++版)在线评测系统 (ssoier.cn)

思路是二分长度,看看这个长度下的做题时间内是否超过t(跟上题烽火台一样),不超过t的时间最少为,假如超过说明长度要扩大,否则减少长度,由于找区间右边界,所以用二分模板1

#include<bits/stdc++.h>
using namespace std;
const int N=5e4+10;
int a[N],f[N],q[N];
int n,m;
bool check(int k)
{
    memset(f,0,sizeof f);//清空上一次的状态
    //下面做法跟烽火台一样
    //但是这里相距k,则有k+1个元素,所以比烽火台+1
    int hh=0,tt=0;
    for(int i=1;i<=n;i++)
    {
        if(i-q[hh]>k+1) hh++;
        f[i]=f[q[hh]]+a[i];
        while(hh<=tt&&f[q[tt]]>=f[i]) tt--;
        q[++tt]=i;
    }
    for(int i=n-k;i<=n;i++)
        if(f[i]<=m) return true;//假如有符合的则返回这个成立
    return false;//否则不成立
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>a[i];
    int l=0,r=n;
    while(l<r)//右边界的二分
    {
        int mid=l+r>>1;
        if(check(mid)) r=mid;//假如符合,则缩小长度
        else l=mid+1;//反之长度加大
    }
    cout<<r<<endl;//输出最后的长度
   return 0;
}

 

5.修剪草坪

信息学奥赛一本通(C++版)在线评测系统 (ssoier.cn)

 

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
ll s[N],f[N];
int q[N];
int n,m;
ll g(int i)//用来算f[i-j-1]-s[i-j]
{
    return f[i-1]-s[i];
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>s[i],s[i]+=s[i-1];
    int hh=0,tt=0;//刚开始有一个元素g[0]=0,意思是前0个花费为0
    for(int i=1;i<=n;i++)
    {
        if(i-q[hh]>m) hh++;//假如滑出窗口
        f[i]=max(f[i-1],g(q[hh])+s[i]);//状态计算
        while(hh<=tt&&g(q[tt])<=g(i)) tt--;//只要最大值
        q[++tt]=i;//当前数入队
    }
    cout<<f[n]<<endl;//输出前n个且合法的情况
   return 0;
}

 

6.理想的正方形

信息学奥赛一本通(C++版)在线评测系统 (ssoier.cn)

先处理出来行方向为n的最大值,在处理纵方向n的最大值,也即该整个正方形的最大值

最小值也同理

#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10;
int a[N][N];
int n,m,k;
int row_max[N][N],row_min[N][N];
int q[N];
//求某个滑动区间的最小值
void get_min(int a[],int b[],int tot)//由于传的是地址,所以下面改变,原来的数组跟着改变
{
    int hh=0,tt=-1;
    for(int i=1;i<=tot;i++)
    {
        if(hh<=tt&&i-q[hh]>=k) hh++;
        while(hh<=tt&&a[q[tt]]>=a[i]) tt--;
        q[++tt]=i;
        b[i]=a[q[hh]];
    }
}
//求某个滑动区间的最大值
void get_max(int a[],int b[],int tot)
{
    int hh=0,tt=-1;
    for(int i=1;i<=tot;i++)
    {
        if(hh<=tt&&i-q[hh]>=k) hh++;
        while(hh<=tt&&a[q[tt]]<=a[i]) tt--;
        q[++tt]=i;
        b[i]=a[q[hh]];
    }
}
int main()
{
   scanf("%d%d%d",&n,&m,&k);
   for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
      scanf("%d",&a[i][j]);
   for(int i=1;i<=n;i++)//先求一遍行的最大与最小值
   {
       get_min(a[i],row_min[i],m);
       get_max(a[i],row_max[i],m);
   }
   int ans=0x3f3f3f3f;
   int a[N],b[N],c[N];//先定义一个临时数组来存数
   for(int i=k;i<=m;i++)//枚举每一列的滑动区间但是最值
   {
       for(int j=1;j<=n;j++) a[j]=row_min[j][i];//先处理出来该列的最小值
       get_min(a,b,n);//在求每一个正方形n的最小值,并存给b
       for(int j=1;j<=n;j++) a[j]=row_max[j][i];//先处理出来该列的最大值
       get_max(a,c,n);//在求每一个正方形n的最大值,并存给c
       for(int j=k;j<=n;j++) ans=min(ans,c[j]-b[j]);//最后枚举该列的每个正方形的差值,并求最小
   }
   printf("%d\n",ans);
   return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值