线性数据结构应用

单调栈

题目大意

给定一个直方图,求直方图中的最大矩形面积

思路

求最大矩形面积,要先求出直方图中每一个矩形的面积,实际上就是以题目中给出的矩形的高为根据,求出对应的底的长度。求底的长度,实际上就是要在横轴上找到左右端点,左端点为往左数第一个小于此高度的点(高度变小,不能形成一个矩形;只有大于等于此高度才可以);右端点为往右数第一个小于此高度的点。两个端点之间的距离,就是底的长度。而求左右端点的过程,正好可以用单调递减栈两遍处理来完成。

代码
  1. 这里采用的是自己手写的栈,访问删除更方便。
  2. 放入栈中的是每个高对应的位置,而不是具体的数值,这样更方便确定左右端点。
#include<bits/stdc++.h>
using namespace std;
long long h[100099];
int lh[100099];
int rh[100099];
int a[100099]; //单减栈 
int n =0;
void solve()
{
  int top=0;
  long long ans=0;
  for(int i=1;i<=n;i++) // 向右走,找出i右边第一个比自己小的 
 {
  if(top==0) a[++top]=i;
  else
  {
   while(top!=0&&h[a[top]]>h[i]) 
   {
    rh[a[top]]=i;
    top--;
   }
    a[++top]=i;
  } 
 }
  
  while(top>0) //清栈 
  {
   rh[a[top]]=n+1;
   top--;
  }
//  for(int i=1;i<=n;i++) cout<<" "<<rh[i];
//  cout<<endl;
  for(int j = n ;j>=1;j--) //往左走,寻找左边第一个小于自己的元素 
  {
   if(top==0) a[++top]=j;
   else{
    while(top>0&&h[a[top]]>h[j]) 
    {
     lh[a[top]]=j;
     top--;
    }
    a[++top]=j;
   }
  }
  while(top>0) //清栈 
  {
   lh[a[top]]=0;
   top--;
  }
//  for(int i=1;i<=n;i++) cout<<" "<<lh[i];
//  cout<<endl;
  for(int i=1;i<=n;i++)
  {
   ans = max(ans,h[i]*(rh[i]-lh[i]-1));
  }
  printf("%lld\n",ans);
}
 int main()
 {
//  freopen("in.txt","r",stdin); 
  while(1)
  {
   scanf("%d",&n);
   if(n==0)
   {
    break;
   }
   memset(lh,0,sizeof(int) *(n+10));
   memset(rh,0,sizeof(int)*(n+10));
   memset(a,0,sizeof(int)*(n+10));
   long long tmp=0;
   for(int i=1;i<=n;i++)
     scanf("%lld",&h[i]);
   solve();
  }
  return 0;
  
 }

单调队列

题目大意

给出一个数组和一个滑动窗口,滑动窗口按数组索引从小到大方向滑动,每次滑动一个单位,求出每次滑动后的窗口中的最大数和最小数。

思路

因为现在需要寻找一个局部长度下的最大和最小值,而不是整体,所以使用单调队列更为合适。分别使用单增和单减队列,将窗口中的元素逐个放到队列中去,判断队尾元素是否小于/大于待入队元素,若不符合则从队尾出队。当窗口里的所有元素都被加入到队列之后,队首元素就是最小/最大元素。

并且,因为窗口是连续滑动的,不需要判断完一个窗口中的最小/最大值之后就清空队列(会TLE),而是通过判断即将入队的元素,和队首元素的在原数组中的位置差值,若超过滑动窗口长度,则需要将队首元素在队首出队。

代码

同样采用手写队列,队列中放入元素数组下标的方法。

#include<cstdio>
#include<cstring>
#include<iostream>
#define mem(a,b) memset(a,b,sizeof a);
#define rep(k,a,b) for(int k=a;k<=b;k++)
using namespace std;
long long a[1000099];
long long q[1000099],front=0,back=0; //单调队列,自队头向队尾有序 
int k=0;
int n=0; 
void solve()
{
 for(int i=1;i<=n;i++)
 {
   while(front<back && a[q[back-1]]>a[i])
   {
    back--;
   }
   q[back++]=i;
   if(i-q[front]>=k) front++; //window move
   if(i>=k) //the first window dont move 
   {
    if(i==k) printf("%lld",a[q[front]]);
          else printf(" %lld",a[q[front]]);
   }
 } 
 cout<<endl;
 front = back =0;
 for(int i=1;i<=n;i++)
 {
   while(front<back && a[q[back-1]]<a[i])
   {
    back--;
   }
   q[back++]=i;
   if(i-q[front]>=k) front++; //window move
   if(i>=k)
   {
    if(i==k) printf("%lld",a[q[front]]);
          else printf(" %lld",a[q[front]]);
   }
 } 
 cout<<endl;
}
int main()
{
// freopen("in.txt","r",stdin);
 mem(a,0);
 mem(q,0);
 scanf("%d%d",&n,&k);
 for(int i=1;i<=n;i++)
 {
  scanf("%lld",a+i);
 }
 solve();
 return 0;
}

尺取法

题目大意

给出一个长度为n 的字符串s,仅包含W,Q,E,R四个字母,如果四种字符在字符串中出现次数均为n/4,则其为一个平衡字符串,现可以将s 中连续的一段替换成任意字符,使其变为一个平衡字符串,问替换子串的最小长度?

思路

这个题之所以可以用尺取法是因为:所求解答案为一个连续区间,并且区间左右端点移动有明确方向:若区间满足要求,则可尝试L++,若不符合要求,则R++,区间越长,满足条件的机率就越大。

具体的步骤为,首先判断一下当前这个串是否已经平衡;若不平衡,则将第一个字符作为代替换子串,求除了这个子串之外的,所有四个字符出现的次数,能否通过在这个子串中替换字符,

  1. 使四类字符和数目最多的那一类字符相等。
  2. 若可以,再需判断子串中剩余的位置,能否被四个字符平均分配。

只有以上两点都满足,这个子串才是合理的,L++,去缩小子串;若不合理,则R++,扩大子串。(若R<L,需要使R++,使之重新满足L<=R)

代码
  1. 使用了一个map来记录各个字符的个数。
  2. 统计子串外四个字符出现的个数的时候,不必每次都重新统计,在以第一个字符为子串的基础上,L++,则L对应的字符个数加一(在子串之外了); R- -,则R对应的子串个数减1(变成了子串中的字符了)
#include<bits/stdc++.h>
using namespace std;
string str;
map<char,int>sum={
 {'Q',0},{'E',0},{'W',0},{'R',0}
} ;
int n=0;
int l=0,r=0,ans=0;
int fmax(int a,int b,int c,int d)
{
 a=a>b? a:b;
 a=a>c? a:c;
 a=a>d?a:d;
 return a;
} 
void cals()
{

	for(int k=0;k<l;k++) sum[str[k]]++;
	
	for(int k=r+1;k<n;k++) sum[str[k]]++;
	
}
int main()
{
//	freopen("in.txt","r",stdin);
    int tl=0,tr=0; 
	cin>>str;
    n = str.size();
    l=n,r=n,ans=n;
    cals();
	if(sum['Q']==sum['W']&&sum['E']==sum['W']&&sum['E']==sum['R']) 
	{
		printf("%d",0);
		return 0;
	}
	r=0;
	l=0;
	sum.clear();
    cals();
    while(r<n)
    {
    	while(l<=r)
    	{
        	int s1=sum['Q'],s2=sum['E'],s3=sum['R'],s4=sum['W'];
         	int MAX =fmax(s1,s2,s3,s4);
    		int space = (r-l+1)-(MAX-s1)-(MAX-s2)-(MAX-s3)-(MAX-s4);
    		if(space>=0&&space%4==0) 
			{
    			ans = min(ans,r-l+1);
    		    tl=l;
    			l++;
    	    	if(l<n) sum[str[tl]]++; //go left num++
			}
    		else {
			  break;	
    		}
		}
    	r++;
    	if(r<n) sum[str[r]]--; //go right num--
//    	cout<<str[r]<<":"<<sum[str[r]]<<endl;
    }
    printf("%d",ans);
//    cout<<" "<<sum['Q']<<" "<<sum['R']<<" "<<sum['W']<<" "<<sum['E']<<endl; 
    return 0;
} 

前缀和差分

题目大意

长度为n 的数组,一共q 次操作,1 ≤ 𝑛, 𝑞 ≤ 1e5, 每次操作给出L, R , c,表示区间[L, R] 中各个数均加上c, 求q 次操作结束后,数组中各个元素值

思路

O(qn)的复杂度会超时,所以直接模拟的做法不可取。按照差分的思想,将对区间的操作,转变为对差分数组的单点操作,设原数组为A,差分数组为B,则
B [ i ] = A [ i ] − A [ i − 1 ] , i ≥ 2 B[i]=A[i]-A[i-1],i\geq 2 B[i]=A[i]A[i1],i2
∑ B [ i ] = A [ i ] \sum B[i] = A[i] B[i]=A[i]
因此,L,R 的区间上,A数组加c,对于B数组来说,只会影响B[L]和B[R+1], 由上第一式,
B[L]会增加c( A [ L ] A[L] A[L]加了c, A [ L − 1 ] A[L-1] A[L1]没有),同理,B[R+1]会减少c,其余B数组的值不受影响(L,R区间内部,加的c两两抵消了),只需要将所有对A的操作全部转化成对B的操作,就可以O(N+q)的实现这一过程,最后再用前缀和,求出A的每一项即可。

代码

注意要用long long

#include<bits/stdc++.h>
using namespace std;
typedef long long llong; //注意了long long 
int n=0;
llong a[200099];
llong b[200099];
int q=0;
int main()
{
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++) scanf("%lld",a+i);
	for(int j=1;j<=n;j++)
	{
		if(j==1) b[1]=a[1];
		else{
			b[j]=a[j]-a[j-1];
		}
	}
	int s=0,e=0,c=0;
	for(int k=0;k<q;k++)
	{
		scanf("%d%d%d",&s,&e,&c);
		b[s]+=c; 
		b[e+1]-=c;
	}
	llong sum=b[1]; 
	for(int i=1;i<=n;i++)
	{
		if(i==1) printf("%lld",sum);
		else{
			sum+=b[i];
			printf(" %lld",sum);
		}
	}
	cout<<endl;
	return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值