【序言】以前还不知道有如此神奇的东西——单调队列!现在做了一中的专题,感觉这东西真的是博大精深。写写题解,是为了加深印象。
1.拥挤的奶牛 (Cow)
题目描述 Description
N头牛在一个坐标轴上,每头牛有个高度。现给出一个距离值D。如果某头牛在它的左边,在距离D的范围内,如果找到某个牛的高度至少是它的两倍,且在右边也能找到这样的牛的话。则此牛会感觉到不舒服。问有多少头会感到不舒服。
输入格式 Input format
第一行两个有空格隔开的整数 n,d ,含义如题中所述。
接下去 n 行,每行两个数字,表示每头牛所站的位置和高度。
输出格式 Output format
一行一个数,表示答案。
样例输入 Sample input
6 4
10 3
6 2
5 3
9 7
3 6
11 2
样例输出 Sample input
2
提示 Hint
1 <= N <= 50,000
1 <= x[i],h[i] <= 1,000,000,000
1 <= D <= 1,000,000,000
【代码】
#include<stdio.h> #include<algorithm> using namespace std; const int maxn=50000+5; struct arr{int x,h;}a[maxn]; int h[maxn],t[maxn],n,d,i,head,tail,f[maxn],ans; bool cmp(arr a,arr b){return a.x<b.x||a.x==b.x&&a.h>b.h;}
//这里特别要注意:有可能有重复位置的奶牛!为了维护单调递减的序列,我们要在坐标相同的情况下,高度从大到小排序。 int main() { freopen("cow.in","r",stdin); freopen("cow.out","w",stdout); scanf("%ld%ld",&n,&d); for (i=1;i<=n;i++) scanf("%ld%ld",&a[i].x,&a[i].h); sort(a+1,a+n+1,cmp); head=1;tail=1;t[1]=a[1].x;h[1]=a[1].h; for (i=2;i<=n;i++) { while (head<=tail&&t[head]<a[i].x-d) head++; if (head<=tail&&h[head]>=a[i].h*2) f[i]++; while (tail>=head&&a[i].h>h[tail]) tail--; h[++tail]=a[i].h;t[tail]=a[i].x; } head=1;tail=1;t[1]=a[n].x;h[1]=a[n].h; for (i=n-1;i>0;i--) { while (head<=tail&&t[head]>a[i].x+d) head++; if (head<=tail&&h[head]>=a[i].h*2) f[i]++; while (tail>=head&&a[i].h>h[tail]) tail--; h[++tail]=a[i].h;t[tail]=a[i].x; } for (i=2;i<n;i++) if (f[i]==2) ans++; printf("%ld",ans); return 0; }
2.理想的正方形 (Square)
题目描述 Description
有一个n*m的整数组成的矩阵,现请你从中找出一个k*k的正方形区域,使得该区域所有数中的最大值和最小值的差最小。
输入格式 Input format
第一行为3个整数,分别表示n,m,k的值第二行至第n+1行每行为m个非负整数,表示矩阵中相应位置上的数。每行相邻两数之间用一空格分隔。
输出格式 Output format
仅一个整数,为n*m矩阵中所有“k*k正方形区域中的最大整数和最小整的差值”的最小值。
样例输入 Sample input
5 4 2
1 2 5 6
0 17 16 0
16 17 2 1
2 10 2 1
1 2 2 2
样例输出 Sample input
1
提示 Hint
2<=a,b<=1000,n<=a,n<=b,n<=100
矩阵中的所有数都不超过1,000,000,000
一开始我想法是:每次预处理最左边的正方形(从上到下一共有n-k+1个),然后一点一点向右拓展——每新的一列来的时候,在删除远的点的同时,把K个元素都进队列处理。但是超时了5个点。
想了一下,在最左边的正方形预处理中,不必一个一个扫一遍,而可以从最左上的那个向下拓展而来。那怎么办?是写成那种函数的形式吗?若是最左边的正方形,则向右、下拓展,否则只向右拓展。(复杂度很高嘛!)
在SYC大神的指导下,懂了一个新的方法。其实没必要怎么麻烦。每次只要枚举每行,从左向右做一遍单调队列。再枚举每一列,做一遍单调队列。典型的从一维拓展为二维的思想。
【超时代码】
#include<stdio.h> using namespace std; const int maxn=1000+5; const int maxn2=maxn*maxn; const int INF=2100000000; int x1[maxn2],x2[maxn2],y1[maxn2],y2[maxn2],a[maxn][maxn]; int n,m,k,ans,i,j,up,head1,head2,tail1,tail2; void insert1(int data,int time) { while (data>x1[tail1]&&tail1>=head1) tail1--; x1[++tail1]=data;y1[tail1]=time; } void insert2(int data,int time) { while (data<x2[tail2]&&tail2>=head2) tail2--; x2[++tail2]=data;y2[tail2]=time; } void del(int now) { while (y1[head1]+k<=now&&head1<=tail1) head1++; while (y2[head2]+k<=now&&head2<=tail2) head2++; } int main() { freopen("square.in","r",stdin); freopen("square.out","w",stdout); scanf("%ld%ld%ld",&n,&m,&k); ans=INF; for (i=1;i<=n;i++) for (j=1;j<=m;j++) scanf("%ld",&a[i][j]); for (up=1;up<=n-k+1;up++) { head1=1;tail1=0;head2=1;tail2=0; if (up==8) up=8; for (i=up;i<up+k;i++) for (j=1;j<k;j++) { insert1(a[i][j],j); insert2(a[i][j],j); } for (j=k;j<=m;j++) { del(j); if (ans==4) ans=4; for (i=up;i<up+k;i++) { insert1(a[i][j],j); insert2(a[i][j],j); } if (x1[head1]-x2[head2]<ans) ans=x1[head1]-x2[head2]; } } printf("%ld",ans); return 0; }
#include<stdio.h> using namespace std; const int maxn=1000+5; const int maxn2=maxn*maxn; const int INF=2100000000; int x1[maxn2],x2[maxn2],y1[maxn2],y2[maxn2]; int a[maxn][maxn],max[maxn][maxn],min[maxn][maxn]; int n,m,k,ans,i,j,up,down,head1,head2,tail1,tail2; void insert1(int data,int time) { while (data>x1[tail1]&&tail1>=head1) tail1--; x1[++tail1]=data;y1[tail1]=time; } void insert2(int data,int time) { while (data<x2[tail2]&&tail2>=head2) tail2--; x2[++tail2]=data;y2[tail2]=time; } void del(int now) { while (y1[head1]+k<=now&&head1<=tail1) head1++; while (y2[head2]+k<=now&&head2<=tail2) head2++; } int main() { freopen("square.in","r",stdin); freopen("square.out","w",stdout); scanf("%ld%ld%ld",&n,&m,&k); ans=INF; for (i=1;i<=n;i++) for (j=1;j<=m;j++) scanf("%ld",&a[i][j]); for (up=1;up<=n;up++) { head1=1;tail1=0;head2=1;tail2=0; for (i=1;i<k;i++) { insert1(a[up][i],i); insert2(a[up][i],i); } for (i=k;i<=m;i++) { del(i); insert1(a[up][i],i); insert2(a[up][i],i); max[up][i-k+1]=x1[head1]; min[up][i-k+1]=x2[head2]; } } for (down=1;down<=m-k+1;down++) { head1=1;tail1=0;head2=1;tail2=0; for (i=1;i<k;i++) { insert1(max[i][down],i); insert2(min[i][down],i); } for (i=k;i<=n;i++) { del(i); insert1(max[i][down],i); insert2(min[i][down],i); if (x1[head1]-x2[head2]<ans) ans=x1[head1]-x2[head2]; } } printf("%ld",ans); return 0; }
3.最大数 (Max)
题目描述 Description
现在请求你维护一个数列,要求提供以下两种操作: 1、 查询操作。语法:Q L 功能:查询当前数列中末尾L个数中的最大的数,并输出这个数的值。限制:L不超过当前数列的长度。 2、 插入操作。语法:A n 功能:将n加上t,其中t是最近一次查询操作的答案(如果还未执行过查询操作,则t=0),并将所得结果对一个固定的常数D取模,将所得答案插入到数列的末尾。限制:n是非负整数并且在长整范围内。注意:初始时数列是空的,没有一个数。
输入格式 Input format
第一行两个整数,M和D,其中M表示操作的个数(M <= 200,000),D如上文中所述
输出格式 Output format
对于每一个查询操作,你应该按照顺序依次输出结果,每个结果占一行。
样例输入 Sample input
5 100
A 96
Q 1
A 97
Q 1
Q 2
样例输出 Sample input
96
93
96
【分析与思考】只是BZOJ上的原题,当时在练线段树的时候,我就觉得可以用单调队列做(虽然当时还不知道这种算法)只是单调队列会有点麻烦——还要记录队列里的数是原来总的数中第几个,然后用二分查找。
没有编,反正可以用线段树水过去的!
4.盖房子 (Build)
题目描述 Description
贵族灰最近搞到手了一块 n*m (n,m≤2000)大小的土地,他决定在这片土地上盖一栋房子。不幸的是,在这块土地上有这一些小坑,小坑内灌满了HCl①,贵族灰不会选择这些土地来盖自己的房子。现在贵族灰聘请你来为他选择地基,要求你选择的地基为一个矩形且在这块矩形中不含有任何一个填了 HCl 的小坑,当然了,为了显示自己的贵族身份,贵族灰自然希望地基的面积越大越好。贵族灰答应你在事成之后分给你 100000000...000 % 10 ¥ 的报酬。
输入格式 Input format
第一行两个有空格隔开的整数 n,m ,表示土地的大小。
接下去n行,每行m个字符,#表示这个位置上填满了HCl,*则表示没有。
输出格式 Output format
一行一个数,表示答案。
样例输入 Sample input
5 10
# * # * # # # * # *
# * # * # * * * # *
# # # * # * * * # *
# * # * # * * * # *
# * # * # # # * # #
样例输出 Sample input
9
【分析与思考】据考证,这道题是USACO training6.1.2上类似的题目,可以用极大化的思想做(不知道是不是这个叫单调队列。)我们维护三个数组h[i][j],l[i][j],r[i][j]。h[i][j]表示从i,j这个点(含)向上共有几个连续的‘*’,l[i][j]和r[i][j]分别表示从i,j这条悬线向左或是向右连续的'*’能达到的最远的点的坐标。每次更新答案是,就是ans和(r[i][j]-l[i][j]+1)*h[i][j]。详细预处理看代码。
【代码】
#include<stdio.h> #include<iostream> using namespace std; const int maxn=2000+5; bool a[maxn][maxn]; int h[maxn][maxn],l[maxn][maxn],r[maxn][maxn],tl[maxn],tr[maxn]; char c; int n,m,i,j,ans,temp; int main() { freopen("build.in","r",stdin); freopen("build.out","w",stdout); scanf("%ld%ld",&n,&m); for (i=1;i<=n;i++) { c=getchar(); for (j=1;j<=m;j++) { c=getchar(); if (c=='*') {a[i][j]=true;ans=1;} c=getchar(); } } for (j=1;j<=m;j++) { for (i=1;i<=n;i++) if (a[i][j]) h[i][j]=h[i-1][j]+1; else {l[i][j]=1;r[i][j]=m;h[i][j]=0;} } for (j=1;j<=m;j++) if (a[1][j]) { if (!a[1][j-1]) l[1][j]=j; else l[1][j]=l[1][j-1]; } for (j=m;j>0;j--) if (a[1][j]) { if (!a[1][j+1]) r[1][j]=j; else r[1][j]=r[1][j+1]; } for (i=2;i<=n;i++) { for (j=1;j<=m;j++) if (a[i][j]) { if (tl[j-1]==0) tl[j]=j; else tl[j]=tl[j-1]; } else tl[j]=0; for (j=m;j>0;j--) if (a[i][j]) { if (tr[j+1]==0) tr[j]=j; else tr[j]=tr[j+1]; } else tr[j]=0; for (j=1;j<=m;j++) if (a[i][j]) { l[i][j]=max(l[i-1][j],tl[j]); r[i][j]=min(r[i-1][j],tr[j]); temp=h[i][j]*(r[i][j]-l[i][j]+1); if (temp>ans) ans=temp; } } printf("%ld",ans); return 0; }