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


    尺取法

    引子

    说实话,这部分其实我也才学了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.l<b.l;
    }
    void init(){
    	for(int i=1;i<=n;i++){
    		scanf("%d%d",&a[i].l,&a[i].r);
    	}
    	sort(a+1,a+1+n,cmp);
    	cnt=1,b[1].l=a[1].l,b[1].r=a[1].r;//预处理区间
    	for(int i=2;i<=n;i++){
    		if(a[i].l<=b[cnt].r+1&&a[i].r>b[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
    ",ans);
    	}
    	return 0;
    }
    

    Eg.2

    hdu1937Finding Seats

    数据范围,能给你启示

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

    所以能用尺取法

    具体思路

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

    示例如下

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    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<c&&count(sh,x,head,tail)<k){
                            tail++;//位数如果小tail++
                        }
                        if(tail==c&&count(sh,x,head,tail)<k) break;//这一内不行
                        ans=min(ans,(tail-head+1)*(x-sh+1));//统计答案
                    }
                }
            }
            printf("%d
    ",ans);
        }
    }
    

    Eg.3

    Kirinriki

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

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

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

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <cmath>
    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<len;i++){//从第二个到len-1 
                    int tail=0,now=0,min1=min(i-1,len-i);//算到两边长 
                    for(int head=1;head<=min1;head++){
                        while(tail<min1&&now+abs(c[i-tail-1]-c[i+tail+1])<=m){ 
                            now+=abs(c[i-tail-1]-c[i+tail+1]);
                            tail++;
                        }
                        ans=max(ans,tail-head+1);
                        now-=abs(c[i-head]-c[i+head]);
                    }
                }
            
                for(int i=1;i<len;i++){
                    int tail=0,now=0,min1=min(i,len-i);
                    for(int head=1;head<=min1;head++){
                        while(tail<min1&&now+abs(c[i-tail]-c[i+tail+1])<=m){
                            now=now+abs(c[i-tail]-c[i+tail+1]);
                            tail++;
                        }
                        ans=max(ans,tail-head+1);
                        now-=abs(c[i-head+1]-c[i+head]);
                    }
                }
            printf("%d
    ",ans);
        }
        return 0;
    }
    

    hdu4123 BOb'race

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

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <cmath>
    #include <queue>
    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<int> qmax;
    deque<int> 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<n;i++){
    		scanf("%d%d%d",&x,&y,&z);
    		insert(x,y,z);
    		insert(y,x,z);
    	}
    }
    //我们通过深搜来记录路径
    int dfs(int u) {
    	vis[u]=1;
    	int to=u;
    	for(int i=h[u];i!=-1;i=edge[i].lac) {
    		if(vis[edge[i].to]) continue;//虽然不可能。。。
    		dis[edge[i].to]=edge[i].wg+dis[u];//我们得到to的路径长
    		int ret=dfs(edge[i].to);//我们就去下一个点,得到最大距离
    		if(dis[to]<dis[ret]) to=ret;//修改一波
    	}
    	return to;//如果没有叶子节点,返回其本身
    }
    //第一次写的树的直径
    //我们利用树的直径的性质。。。
    //对于一条直径
    //一个点到别的点的最长路径肯定是在两端点的。。
    //但是我们不知道到那个端点,于是做3次深搜。。
    //前两此的直径,后两次的路径
    void find_dtr() {
    	memset(dis,0,sizeof dis);
    	memset(vis,0,sizeof vis);
    	int s=dfs(1);//返回距离u的最远节点
    	memset(dis,0,sizeof dis);
    	memset(vis,0,sizeof vis);
    	int t=dfs(s);//此时,已经有了一部分答案我们选择memcpy
    	memcpy(ans,dis,sizeof ans);
    	memset(dis,0,sizeof dis);
    	memset(vis,0,sizeof vis);
    	s=dfs(t);//在重新搜回去
    	
    	//事实上,再有多个路径的时候,前一个s可能不是这s,但是这s和t是直径端点。
    	for(int i=1;i<=n;i++) ans[i]=max(ans[i],dis[i]);
    }
    void print(){
    	while(m--){
    		//我们统计m次答案
        //我们做两个单调队列,一个记录最大值的编号,一个记录最小的编号
    		scanf("%d",&k);
    		int head=1,len=-1;
    		for(int i=1;i<=n;i++){//枚举嵬节点
    			while(!qmin.empty()&&ans[i]<=ans[qmin.back()]) qmin.pop_back();//却在前面越小
    			while(!qmax.empty()&&ans[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()<head) qmax.pop_front();
    				if(qmin.front()<head) qmin.pop_front();
    			}
    			len=max(len,i-head+1);
    		}
    		while(!qmin.empty()) qmin.pop_front();
    		while(!qmax.empty()) qmax.pop_front();
    		printf("%d
    ",len);
    	}
    }
    int main() {
    	freopen("Bob.in","r",stdin);
    	while(1){
    		scanf("%d%d",&n,&m);
    		if(n==0&&m==0) break;//程序结束
    		memset(h,-1,sizeof h);cnt=0;//初始化
    		build();//建树
    		find_dtr();//找直径
    		print();//单调队列输出答案
    	}
    	return 0;
    }
    

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

    哼唧

  • 相关阅读:
    升讯威微信营销系统开发实践:(3)功能介绍与此项目推广过程的一些体会( 完整开源于 Github)
    Github 开源:使用 .NET WinForm 开发所见即所得的 IDE 开发环境(Sheng.Winform.IDE)【2.源代码简要说明】
    Github 开源:使用升讯威 Mapper( Sheng.Mapper)与 AutoMapper 互补,大幅提高开发效率!
    Github 开源:高效好用的对象间属性拷贝工具:升讯威 Mapper( Sheng.Mapper)
    Github 开源:使用控制器操作 WinForm/WPF 控件( Sheng.Winform.Controls.Controller)
    Github 开源:升讯威 Winform 开源控件库( Sheng.Winform.Controls)
    解决 mysql from_base64 函数返回乱码的问题
    最新版Python开发环境搭建
    判断多边形(含凸多边形)是顺时针方向还是逆时针方向
    vertica 列转行的sql
  • 原文地址:https://www.cnblogs.com/zhltao/p/12274405.html
Copyright © 2020-2023  润新知