题意:
n个数组成的序列环,求长度小于等于m的字串的最大区间和。
思路:
如果n个数为线性关系,可以用单调队列维护第i个数之前的m个数,
及时剔除没用的数。o(N)解决。
这里是环,2倍数组破环成链即可。(代码非自己写)
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define maxn 1000005
int n,m,head,rear,ans;
int a[maxn*2],q[maxn*2];
void in_queue(int x)
{
while(head<=rear && a[x]<=a[q[rear]])//删除队中大于等于它的元素
rear--;
q[++rear]=x;//满足条件则下标入队
}
void out_queue(int x)
{
if(q[head]<x-n+1)
head++;
}
int main()
{
int i;
while(scanf("%d",&n),n!=0)
{
head=ans=0;
rear=-1;
for(i=1;i<=n;i++)
{
scanf("%d",&a[i]);
a[n+i]=a[i];
}
for(i=2;i<2*n;i++)
a[i]+=a[i-1];
for(i=1;i<n;i++)
in_queue(i);
for(i=n;i<2*n;i++) //依次求区间[i-n+1,i]中的最小值
{
in_queue(i);
out_queue(i);
if(a[q[head]]-a[i-n]>=0)//如果最小值都大于0,计数
ans++;
}
printf("%d\n",ans);
}
}
题意:
由字母C和J组成的序列环,可以从任意一个地方断开,
要求断开后向右或者向左便利序列的过程中保持Number C>=J,
求满足要求的切割方案数。
思路:
暴力:破环成链,枚举终点(或者中点起点都可以),看终点往前的n个数是否满足要求。O(N*N);
思考:
n个数满足要求即n个数所有以第一个数为起点的字串和均>=0,或者说其中
最小前缀和-第一个数-1的前缀和>=0,所以问题变为求以第i个数为起点,长度最
长为n的的字串中最小 的前缀和。
也可以转换为求以第i个数为末尾,长度不超过n的字串中最小的前缀和。只要
起点到该点的序列和>=0 ,则段序列对应的切割点为可行方案。(PS:最开使看的
模板题也是求以第i个数为终点,长度不超过m的字串中最小的/最大的前缀和,(
用单调队列来维护一个长度小于m的序列,该序列只在队头和队尾修改)所以这
题其实是模板的一个变式,如果直接从原理上思考的话不太好考虑)。
#include<bits/stdc++.h>
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
const int maxn=2e6+50;
int n,m,k;
int tail,head;
int stk[maxn];
int ans;
int a[maxn];
int c[maxn];
char s[maxn];
bool jd[maxn];
int main()
{
int T,cas=1;
scanf("%d",&T);
getchar();
while(T--)
{
memset(jd,0,sizeof(jd));
scanf("%s",s+1);
n=strlen(s+1);
for(int i=1;i<=n;i++)
{
a[i]=(s[i]=='C')?1:-1;
a[n+i]=a[i];
}
c[0]=0;
for(int i=1;i<=2*n;i++)
{
c[i]=c[i-1]+a[i];
}
ans=0;
head=-1;tail=0;
for(int i=1;i<2*n;i++)
{
while(head>=tail&&c[i]<c[stk[head]]) head--;
stk[++head]=i;
while(head>tail&&i-stk[tail]>=n) tail++;
if(i>=n&&c[stk[tail]]-c[i-n]>=0) jd[i-n+1]=1;
}
c[0]=0;
for(int i=1;i<=2*n;i++)
{
int k=n+n-i+1;
c[i]=c[i-1]+a[k];
}
head=-1;tail=0;
for(int i=1;i<=2*n;i++)// i<=2*n
{
while(head>=tail&&c[i]<c[stk[head]]) head--;
stk[++head]=i;
while(head>tail&&i-stk[tail]>=n) tail++;
if(i>n&&c[stk[tail]]-c[i-n]>=0) jd[n-(i-n)+1]=1;//i>n 注意正反切割地点的下标要一致
}
for(int i=1;i<=n;i++)
if(jd[i]) ans++;
printf("Case %d: %d\n",cas++,ans);
}
return 0;
}
题意:n个数,求最长 字串 的长度,且该字串满足,N<=1e5。
思路:
暴力:枚举所有子串,计算最大值最小值,O(N*N*N)/,虽然线段树不会,不过线段树优化的话也是O(N*N*logN)。
本质是求子串的最值,所以目前来说有O(N)的单调队列解法。所以考虑开两个队列,
一个递增、一个递减,两者取队头坐标小者作为左边界,i是右边界。当maxs-mini小于m时
进入下一个i,大于k时,调整队列头指针位置,调整谁的呢,就问一句:调整index大的有
用么,调整后原来的对头不照样被区间包含。然后更新ans即可,ans=max(ans,i-L+1+1)
(理想状态下len=不合法的len-1,不行的时候len'不再是len-2,要根据此时的L确定)。
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
const int MAXN=100010;
int q1[MAXN],q2[MAXN];
int rear1,head1;
int rear2,head2;
int a[MAXN];
int main()
{
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
int n,m,k;
while(scanf("%d%d%d",&n,&m,&k)!=EOF)
{
rear1=head1=0;
rear2=head2=0;
int ans=0;
int now=1;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
while(head1<rear1&&a[q1[rear1-1]]<a[i])rear1--;//这里的等号取和不取都可以的
while(head2<rear2&&a[q2[rear2-1]]>a[i])rear2--;
q1[rear1++]=i;
q2[rear2++]=i;
while(head1<rear1&&head2<rear2&&a[q1[head1]]-a[q2[head2]]>k)
{
if(q1[head1]<q2[head2])now=q1[head1++]+1;
else now=q2[head2++]+1;
}
if(head1<rear1&&head2<rear2&&a[q1[head1]]-a[q2[head2]]>=m)
{
//int t=min(q1[head1],q2[head2]);
if(ans<i-now+1)ans=i-now+1;
}
}
printf("%d\n",ans);
}
return 0;
}
4、HDU-4122(二维优先队列)
题意:
n分订单,只在m个时刻做蛋糕(第一个时刻:0~1hour);
n份订单要提交的时刻,每份订单所需要蛋糕的数目;
每个蛋糕自生产时刻起能够保存几个小时,保存一个小时所需要的花费;
m个生产时刻生产蛋糕时生产一个所需要的花费;
N<=2500,M<=1e5,,,
自己的思路:
O(M)遍历M序列,所需维护的元素最多T个,且维护过程的增删为“窗口滑动”型,
所以队列维族该集合,具体维护以 i 为终点,长度不超过T的字串中花费的最小值,
显然
若在一个时刻k后有比其花费小的时刻j,那j之后所有的 时刻中k时刻都不会作为最小花费
被选择,k为无用信息,去掉后即为单调增队列数据结构。(直接单调队列维护)。最后遍历所有订单提交时刻,统计花费总数。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<queue>
#include<algorithm>
#include<map>
#include<iomanip>
#define INF 99999999
using namespace std;
const int MAX=2500+10;
char mon[5];
int M[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
struct Node{
int num,t;
//对于订单来说num表示月饼数量,t表示订单时间;
//对于制作月饼来说num表示耗费的价值,t表示制作时间
}order[MAX],q[MAX*4];//q表示单调递增队列
int MON(char *m){
if(strcmp(m,"Jan") == 0)return 1;
if(strcmp(m,"Feb") == 0)return 2;
if(strcmp(m,"Mar") == 0)return 3;
if(strcmp(m,"Apr") == 0)return 4;
if(strcmp(m,"May") == 0)return 5;
if(strcmp(m,"Jun") == 0)return 6;
if(strcmp(m,"Jul") == 0)return 7;
if(strcmp(m,"Aug") == 0)return 8;
if(strcmp(m,"Sep") == 0)return 9;
if(strcmp(m,"Oct") == 0)return 10;
if(strcmp(m,"Nov") == 0)return 11;
return 12;
}
bool LeapYear(int &year){
return year%4 == 0 && year%100 || year%400 == 0;
}
int Time(int &year,int Mon,int &d,int &h){
int t=0;
for(int i=2000;i<year;++i){
if(LeapYear(i))t+=366;
else t+=365;
}
bool flag=LeapYear(year);
for(int i=1;i<Mon;++i){
if(flag && i == 2)t+=29;
else t+=M[i];
}
t+=d-1;
return t*24+h;
}
int main(){
int n,m,t,s,r,h,d,year,a;
while(~scanf("%d%d",&n,&m),n+m){
for(int i=0;i<n;++i){
scanf("%s%d%d%d%d",mon,&d,&year,&h,&r);
order[i].num=r,order[i].t=Time(year,MON(mon),d,h);
//转化成小时
}
int top=0,tail=0,p=0;
__int64 sum=0;
scanf("%d%d",&t,&s);
for(int i=0;i<m;++i){
scanf("%d",&a);
while(top<tail && q[tail-1].num+(i-q[tail-1].t)*s>=a)--tail;
q[tail].num=a,q[tail++].t=i;
while(p<n && i == order[p].t){
while(q[top].t+t<i)++top;
sum+=(q[top].num+(i-q[top].t)*s)*order[p++].num;
}
}
printf("%I64d\n",sum);
}
return 0;
}
5、FZU- Problem 2080
题意:给定一个的矩阵,从其中所有子矩阵的最大值-最小值中选一个最大的作为结果。
思路:
首先,对于这种统计最值的问题可以用遍历所有元素来解决,但如果是求字串的最值,
遍历的话需要o(n*n)的时间复杂度,所以我们引入了单调队列,其
实质就是求一段
序列各种字串的最值。(单调队列最常解决的问题)对应到矩阵上来,求所有子矩阵的最值,仍然可以采取遍历的方式,但是时间复杂度
O(N*M*C*R) ,然后另一种求矩阵最值的方案,先求出每列的最值,然后取其中最值就是整
个矩阵的最值(要从整个矩阵上考虑,枚举每一个子阵的话显然更慢),仍然按照朴素算
法来算的话,时间复杂度O(M列*N起点*Len长度*N遍历)+O(N行*M起点*len长度*M遍历)
(该题中Len为固定值,可以忽略),1second肯定TLE了,考虑这实际上就是在求序列字串
的最值,属于单调队列的应用之一,所以对整个矩阵操作,时间复杂度为O(M*N)+O(N*M)
,,,完美。
#include<iostream>
#include<cstdio>
#include<map>
#include<cstring>
#include<vector>
#include<queue>
#include<algorithm>
#include<cmath>
#include<set>
using namespace std;
#define N 1005
int head,tail,mat[N][N],vmax[N][N],vmin[N][N];
//vmax[i][j]表示(i,j)到(i+d-1,j+d-1)中的最大值,
//vmin[i][j]表示(i,j)到(i+d-1,j+d-1)中的最小值。
struct node
{
int num,id;
};
node que[N];
void add_max(int num,int id)
{
while(head<tail&&que[tail-1].num<=num)tail--; //单调队列维护
que[tail].id=id;
que[tail++].num=num;
}
void add_min(int num,int id)
{
while(head<tail&&que[tail-1].num>=num)tail--;
que[tail].id=id;
que[tail++].num=num;
}
int get_num(int id)
{
while(head<tail&&que[head].id<id)head++;
return que[head].num;
}
int main()
{
//freopen("a.txt","r",stdin);
int n,m,r,c;
while(scanf("%d%d%d%d",&n,&m,&r,&c)!=EOF)
{
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
scanf("%d",&mat[i][j]);
}
}
for(int i=0;i<m;i++)
{
head=tail=0;
for(int j=0;j<r-1;j++)add_max(mat[j][i],j);
for(int j=r-1;j<n;j++)
{
add_max(mat[j][i],j);
vmax[j-r+1][i]=get_num(j-r+1);
//这时的vmax[j-r+1][i]表示第i列中的第j-r+1行到第j行的最大值
}
}
for(int i=0;i<=n-r;i++)
{
head=tail=0;
for(int j=0;j<c-1;j++)add_max(vmax[i][j],j);
//这里就是二维单调队列维护了
for(int j=c-1;j<m;j++)
{
add_max(vmax[i][j],j);
vmax[i][j-c+1]=get_num(j-c+1);
}
}
for(int i=0;i<m;i++)
{
head=tail=0;
for(int j=0;j<r-1;j++)add_min(mat[j][i],j);
for(int j=r-1;j<n;j++)
{
add_min(mat[j][i],j);
vmin[j-r+1][i]=get_num(j-r+1);
}
}
for(int i=0;i<=n-r;i++)
{
head=tail=0;
for(int j=0;j<c-1;j++)add_min(vmin[i][j],j);
for(int j=c-1;j<m;j++)
{
add_min(vmin[i][j],j);
vmin[i][j-c+1]=get_num(j-c+1);
}
}
int ans=0;
for(int i=0;i<=n-r;i++)
{
for(int j=0;j<=m-c;j++)
{
ans=max(ans,vmax[i][j]-vmin[i][j]);
}
}
printf("%d\n",ans);
}
return 0;
}
The end;