单调队列(尺取法) 学习笔记

尺取法

引子

说实话,这部分其实我也才学了3天,刚开始接触时,是做了一个小小粉丝嘟嘟熊_hdu6119,听T老师讲的时候,感觉跟之前做的斜率优化,就是我之前写的HNOI的玩具装箱 ,差不多,都是用了一个单调队列,来优化,其实重要的可以应用的原因是wyq所说的单调

我们来看看一个明显的单调队列的例子

Eg. 有这么一行数\(a_1,a_2,a_3,...,a_n\),我们要求所有任意连续k个数中的最小值。

我们平常的方法是什么,枚举一个起点\(head\)循环到\(head+k-1\),求出其中的最小值 ,再把所有的最小值比较,即可得出答案,时间复杂度是\(O(kn)\)

用了单调法,\(O(kn)\)\(O(n)\)

主要分为以下几步

  1. 初始化 \(head=1,tail=0,a_{0}=0x3f3f3f3f\),循环自变量为\(c\)
  2. 检查\(que_{head}\) 是否\(>=tail-k+1\) 若是,继续,否则,\(head++\),直到满足条件
  3. \(a_{c}\)满足小于等于\(a_{que_{tail}}\)\(head<=tail\),,\(tail--\)
  4. \(c\)入队
  5. \(c>=k\)时,\(a_{que_{head}}\)即为所求

这是单调队列的简单应用,我们来进入正题

正文

用我的第一道题做第一道例题吧

小小粉丝嘟嘟熊_hdu6119

我们先不管算法,来谈谈如何合并一个区间

我的初步想法是这样的,先把一个个区间按照左端点排序,左端点相同,按右端点从小至大,然后扫一遍就行了‘

然后尺取,做一个\(tail\)\(head\),然后,你既然想要最大的连续的,那么头指针单调时,tail必定单调,所以我们能使用尺取法

接下来是常规的尺取,算区间的间隔,看小不小k

bool cmp(Node a,Node b){
    if(a.l==b.l) return a.r>b.r;
    return a.lb[cnt].r) b[cnt].r=a[i].r;
        if(a[i].l>b[cnt].r){
            cnt++;
            b[cnt].l=a[i].l,b[cnt].r=a[i].r;
        }
    }
    memset(que,0,sizeof que);
    head=1;
    ans=b[1].r-b[1].l+1+k;
    kong=0;
    return ;
}
int main(){
    while(scanf("%d%d",&n,&k)!=EOF){
        init();
        for(int i=2;i<=cnt;i++){//尺取法
            while(head<=i&&kong+b[i].l-b[i].r-1>k){
                kong=kong-(b[head+1].l-1-b[head].r);
                head++;
            }
            kong=kong+(b[i].l-b[i-1].r-1);
            ans=max(ans,k-kong+b[i].r-b[head].l+1);
        }
        printf("%d\n",ans);
    }
    return 0;
}

Eg.2

hdu1937Finding Seats

数据范围,能给你启示

我们如果平常的,枚举一个矩阵左上,右下的话,时\(O(n^6)\),会超时,考虑到我们的座位,再上界,下界,左界一定时随着右界的增大,Seats 是不减函数,在做前缀和,就能把时间复杂度降为\(O(n^3)\)可以在规定时间内过掉

所以能用尺取法

具体思路

枚举上界和下界,对其中用尺取法,注意都是闭区间。。。

示例如下

#include 
#include 
#include 
const int Maxn=303;
int r,c,k,s[Maxn][Maxn],ans;
char ch;
using namespace std;
int read(){
    int x=0;
    char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch<='9'&&ch>='0'){
        x=(x<<1)+(x<<3)+(ch-'0');
        ch=getchar();
    }
    return x;
}

int count(int y1,int y2,int x1,int x2){
    return s[y2][x2]-s[y2][x1-1]-s[y1-1][x2]+s[y1-1][x1-1];
}
int main(){
    //freopen("hdu1937.in","r",stdin);
    while(1){
        scanf("%d%d%d%c",&r,&c,&k,&ch);
        if(r==0&&c==0&&k==0)break;
        memset(s,0,sizeof s);
        for(int i=1;i<=r;i++){//制作前缀和
            for(int j=1;j<=c;j++){
                ch=getchar();
                if(ch=='X')    s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
                else s[i][j]=1+s[i-1][j]+s[i][j-1]-s[i-1][j-1];
            }
            ch=getchar();
        }
        ans=0x7fffffff;
        for(int sh=1;sh<=r;sh++){
            for(int x=sh;x<=r;x++){//枚举上界下界
                int tail=0;//枚举head
                for(int head=1;head<=c;head++){
                    while(tail

Eg.3

Kirinriki

这个题,他告诉你我们的分数会加绝对值,不减

而且 他是一个轴对称的算法,并且不能有交集

我们可以用尺取法来解决,枚举每个对称轴,并不是有奇偶性之分

#include 
#include 
#include 
#include 
using namespace std;
const int Maxn=100000;
int T,m;
char c[Maxn];


int main(){
    //freopen("hdu6103.in","r",stdin);
    scanf("%d",&T);
    while(T--){
        scanf("%d",&m);//读入 
        scanf("%s",c+1);    
        int ans=-1,len=strlen(c+1);
        
            for(int i=2;i

hdu4123 BOb'race

我们看看,这道题就是求一个点在树图上所能到达的最远距离,我们知道,这个距离就是到任意一条直径两端点的较大距离
这样我们就得出了每个点的答案,然后用两个单调队列,一个存最大值的编号,一个存最小值的编号,O(n)输出答案。

#include 
#include 
#include 
#include 
#include 
using namespace std;
const int Maxn=50005;
struct Node{
    int lac,to,wg;
}edge[Maxn*2];
int n,m,cnt,h[Maxn],x,y,z,dis[Maxn],ans[Maxn],k;
deque qmax;
deque qmin;
bool vis[Maxn];
void insert(int x,int y,int z);
void find_dtr();
void build();
int dfs(int u);
void print();
void insert(int x,int y,int z){
    edge[cnt].lac=h[x];
    edge[cnt].to=y;
    edge[cnt].wg=z;
    h[x]=cnt++;
}
void build(){
    for(int i=1;i=ans[qmax.back()]) qmax.pop_back();//越在前面越大
            qmin.push_back(i);
            qmax.push_back(i);
      while(ans[qmax.front()]-ans[qmin.front()]>k){
                head++;
                if(qmax.front()

其实,有些改变的题还有很多 像CF那道删数,就要做个邻接表

哼唧

你可能感兴趣的:(单调队列(尺取法) 学习笔记)