程序设计——第五周作业(单增栈:最大矩形,差分:魔法猫,尺取:平衡字符串,单调队列:滑动窗口)

A:最大矩形

题目描述:

给一个直方图,求直方图中的最大矩形的面积。例如,下面这个图片中直方图的高度从左到右分别是2, 1, 4, 5, 1, 3, 3, 他们的宽都是1,其中最大的矩形是阴影部分。
在这里插入图片描述
Input:
输入包含多组数据。每组数据用一个整数n来表示直方图中小矩形的个数,你可以假定1 <= n <= 100000. 然后接下来n个整数h1, …, hn, 满足 0 <= hi <= 1000000000. 这些数字表示直方图中从左到右每个小矩形的高度,每个小矩形的宽度为1。 测试数据以0结尾。
Output:
对于每组测试数据输出一行一个整数表示答案。
Sample Input:

 7 2 1 4 5 1 3 3
 4 1000 1000 1000 1000
 0

Sample Output:

8
4000

思路分析:

所有的小矩形的宽度都为1,那么我一旦确定了当前矩形的高度,我就要尽量把这个矩形往左右两边延伸,来获取最大的宽度,但是如何进行左右扩展呢?
维护一个单调递增的栈,定义一个数组st表示栈数组,定义一个数组w表示当前栈顶矩形的宽度(可以拓展开来的矩形个数),对每个矩形的高进行如下处理:
(1)如果这个即将入栈的矩形的高比栈顶矩形高值大,直接入栈,入栈后,由于新的栈顶矩形的高是当前栈中最高的,那么如果以这个栈顶矩形的高为我们要选择的结果矩形高标准,就不能往左边扩展矩形,更不能往右扩展(这个矩形已经是栈顶了,没有右边可以给它扩展了)。
(2)如果即将入栈的矩形的高比栈顶矩形的高低,那么就依次弹出栈顶直到栈顶矩形比即将入栈的矩形低,同时注意,弹出栈顶矩形意味着当前即将入栈的矩形可以往左边边扩展,计算出以每个弹出的矩形高为结果矩形的高标准的宽(这些矩形在入栈的时候,已经充分考虑统计过做扩展的宽度并且存在w中,现在弹出过程其实就是在统计右扩展的宽度),比并且选出最大的面积,弹栈完毕后,将新矩形入栈,这个新栈顶矩形可以向左扩展,以这个矩形高为结果高标准的矩形宽度就是弹出矩形矩形的宽再加上自己本身的宽度1。
对所有输入的高进行上述操作后,最后统计出来的面积就是最大矩形面积。
另外要注意:这个题是多组测试数据,所以一定要循环内对数组进行初始化!

代码实现:

//核心:新入栈的矩形只考虑左扩展、弹出栈的矩形只考虑向右扩展 
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

long long st[100001],h[100001],w[100001];//分别表示栈数组、高数组、宽度

int main()
{
	int n,top=0;//top表示栈数组的顶部索引 
	while(~scanf("%d",&n))
	{
		if(n==0) return 0;
		memset(st,0,100001);//初始化数组 
	    memset(h,0,100001);
	    memset(w,0,100001);
	    top=0;
	    long long max_s=0;
		
		for(int i=1;i<=n;i++)
		    scanf("%lld",&h[i]);//输入高
		
		//入栈
		for(int i=1;i<=n+1;i++)
		{
		    //printf("h[%d]:%d\n",i,h[i]);
			if(st[top]<h[i])//直接入栈
		    {
			    //栈中第一个矩形高度初始为0 
			    st[++top]=h[i];//栈中的矩形的高度都比h[i]小,
			    w[top]=1;//所以新入栈的矩形不能扩展 
			    //printf("st[%d]:%d,",top,st[top]);
			    //printf("w[%d]:%d\n",top,w[top]);
	        }
	        else
	        {
	    	    int popw=0;//累计弹出矩形的宽度 
			    while(st[top]>h[i])//弹出栈
			    {
				    popw+=w[top];//表示当前弹出栈的矩形向右边边扩展 
				    max_s=max(max_s,popw*st[top]);//
				    //printf("popw:%d,poph:%d\n",popw,st[top]);
				    //printf("max_s:%d\n",max_s);
				    top--;//弹出栈 
			    }
			    st[++top]=h[i];
			    w[top]=popw+1;//可以向图中的左边扩展 
			    //printf("st[%d]:%d,",top,st[top]);
			    //printf("w[%d]:%d\n",top,w[top]);
			    
		    }
	    }
	    printf("%lld\n",max_s);
	}
	return 0;
} 

A题总结:

思路很简单都是课上讲过的,但是要注意:一定要对栈数组、宽度数组进行初始化以及数据类型定义为long long,矩形的高和宽度相乘后的结果数据类型用int 是完全不够的!

B:TT的魔法猫

题目描述:

Thanks to everyone’s help last week, TT finally got a cute cat. But what TT didn’t expect is that this is a magic cat.One day, the magic cat decided to investigate TT’s ability by giving a problem to him. That is select n cities from the world map, and a[i] represents the asset value owned by the i-th city.Then the magic cat will perform several operations. Each turn is to choose the city in the interval [l,r] and increase their asset value by c. And finally, it is required to give the asset value of each city after q operations.Could you help TT find the answer?
Input:
The first line contains two integers n,q(1≤n,q≤2⋅105)— the number of cities and operations.The second line contains elements of the sequence a: integer numbers a1,a2,…,an (−106≤ai≤106)Then q lines follow, each line represents an operation. The i-th line contains three integers l,r and c (1≤l≤r≤n,−105≤c≤105) for the i-th operation.
Output:
Print n integers a1,a2,…,an one per line, and ai should be equal to the final asset value of the i-th city.
大概意思是:长度为n的数组,一共q次操作,每次操作给出l,r,c,表示在位置区间[l,r]中各个数均加c,输出操作后数组各值。
注意:这个位置范围并不是从0开始,题意的意思是从1开始的。

思路分析:

如果暴力做法对每个范围的数字一个一个地进行操作,复杂度达到O(qn),对于这道题的测试数据个数,一定会超时。所以这里就可以用课堂上讲过的差分法:
原数组A,定义一个数组B,B[1]=A[1];B[i]=A[i]-A[i-1] (1<=i<=n);B[n+1]=0-A[n]。对A的一个范围内的数字进行操作后,B[l+1:r]的值肯定是不变的,B[l]=A[I]-A[l-1]值会加c,B[r+1]=A[r+1]-A[r]会减c。最后通过A[i]=B[i]+A[i-1]就可以计算出A数组的新值。

代码实现:

#include<cstdio>
#include<cstring>
using namespace std;

long long a[200010],b[200020];
long long l,r,c,n,q;

int main()
{
	scanf("%lld%lld",&n,&q);
	memset(a,0,200010);
	for(int i=1;i<=n;i++)//从1位置开始输入 
	{
	    scanf("%lld",&a[i]);
        b[i]=a[i]-a[i-1];
	}
	b[n+1]=0-a[n];
	
	for(int j=0;j<q;j++)
	{
		scanf("%lld%lld%lld",&l,&r,&c);
		b[l]+=c;
		b[r+1]-=c;
	}
	for(int i=1;i<=n;i++)
	{
		a[i]=b[i]+a[i-1];
		printf("%lld ",a[i]);
	}
	return 0;
}

C:平衡字符串

题目描述:

一个长度为 n 的字符串 s,其中仅包含 ‘Q’, ‘W’, ‘E’, ‘R’ 四种字符。如果四种字符在字符串中出现次数均为 n/4,则其为一个平衡字符串。现可以将 s 中连续的一段子串替换成相同长度的只包含那四个字符的任意字符串,使其变为一个平衡字符串,问替换子串的最小长度?
如果 s 已经平衡则输出0。
Input:
一行字符表示给定的字符串s
Output:
一个整数表示答案
Examples:
Input:

QWER

Output:

0

Input:

QQWE

Output:

1

思路分析:

这个题所有答案是一个连续区间,区间左右端点移动有明确的方向,所以可以考虑使用尺取法,选择一个区间[L,R],如果满足题意就L++,选择更小的区间保证替换的字符可以更少,如果不满足就R++扩展区间。如何判断区间是否满足替换要求呢?
定义四个变量sum1,sum2,sum3,sum4分别表示[L,R]区间外!!!的Q.W.E.R的出现频率,接下来,就要替换[L,R]区间内的字符来保证整个字符串满足平衡,通过替换使得4个字符的频率一致,那么四个字符的频率至少是MAX=max(sum1,sum2,sum3,sum4),所以需要在[L,R]范围内替换的字符个数就是count=(MAX-sum1)+(MAX-sum2)+(MAX-sum3)+(MAX-sum4),如果替换完毕后,这个区间没有被替换的字符个数free=R-L+1-count的值大于等于0并且可以被4整除那么这个区间就满足替换要求,接下来只需要增加左边界看看是否还有更小的满足替换的区间,使min函数统计min值。否则,就增加右边界扩展区间。
另外:在统计[L,R]区间外四个字符的频率的时候,可以使用前缀和,定义四个数组countq,countw,counte,countr,索引为i的值对应第0到i位置该字符出现次数。

代码实现:

#include<cstdio>
#include<algorithm> 
#include<cstring>
using namespace std;

int countq[100000],counte[100000],countw[100000],countr[100000];
char str[100000];

int main()
{
	//memset(str,'Z',100000);//初始化为Z 
	scanf("%s",str);
	countq[0]=0,counte[0]=0,countw[0]=0,countr[0]=0;
	if(str[0]=='Q')
	    countq[0]=1;
	if(str[0]=='W')
	    countw[0]=1;
	if(str[0]=='E')
	    counte[0]=1;
	if(str[0]=='R')
	    countr[0]=1;
	
	int n=1;
//	for(int i=0;i<20;i++)
	 //   printf("%c\n",str[i]);
	while(str[n]=='Q'||str[n]=='W'||str[n]=='E'||str[n]=='R')
	{
		//printf("%c\n",str[n]);
		if(str[n]=='Q')
		    countq[n]=countq[n-1]+1,countw[n]=countw[n-1],counte[n]=counte[n-1],countr[n]=countr[n-1];
		if(str[n]=='W')
		    countq[n]=countq[n-1],countw[n]=countw[n-1]+1,counte[n]=counte[n-1],countr[n]=countr[n-1];
		if(str[n]=='E')
		    countq[n]=countq[n-1],countw[n]=countw[n-1],counte[n]=counte[n-1]+1,countr[n]=countr[n-1];
		if(str[n]=='R')
		    countq[n]=countq[n-1],countw[n]=countw[n-1],counte[n]=counte[n-1],countr[n]=countr[n-1]+1;
	    n++;
	    //printf("n:%d\n",n);
	   // printf("%d %d %d %d\n",countq[n-1],countw[n-1],counte[n-1],countr[n-1]);
	}
	int l=0,r=0;
	int res=100000;
	while(l<n&&r<n)
	{
		int ql,wl,el,rl;
		if(l==0)
		    ql=0,wl=0,el=0,rl=0;
		else
		    ql=countq[l-1],wl=countw[l-1],el=counte[l-1],rl=countr[l-1];
		int sum1=ql+countq[n-1]-countq[r];
		int sum2=wl+countw[n-1]-countw[r];
		int sum3=el+counte[n-1]-counte[r];
		int sum4=rl+countr[n-1]-countr[r];
	//	printf("%d %d %d %d\n",sum1,sum2,sum3,sum4);
		int total=r-l+1;
	//	printf("total:%d\n",total);
		int themax=max(max(sum1,sum2),max(sum3,sum4));
		int free=total-(themax-sum1)-(themax-sum2)-(themax-sum3)-(themax-sum4);
	//	printf("free:%d\n",free);
		if(free>=0&&free%4==0)
		{
		    res=min(res,total);
			l++;
		}   
		else
		    r++;
	}
	printf("%d",res);
	return 0;
} 

D - 滑动窗口

题目描述:

ZJM 有一个长度为 n 的数列和一个大小为 k 的窗口, 窗口可以在数列上来回移动. 现在 ZJM 想知道在窗口从左往右滑的时候,每次窗口内数的最大值和最小值分别是多少. 例如:
数列是 [1 3 -1 -3 5 3 6 7], 其中 k 等于 3.
在这里插入图片描述
Input:
输入有两行。第一行两个整数n和k分别表示数列的长度和滑动窗口的大小,1<=k<=n<=1000000。第二行有n个整数表示ZJM的数列。
Output:
输出有两行。第一行输出滑动窗口在从左到右的每个位置时,滑动窗口中的最小值。第二行是最大值。
Sample Input:

8 3
1 3 -1 -3 5 3 6 7

Sample Output:

-1 -3 -3 -3 3 3
3 3 5 5 6 7

思路分析:

这里是要查找窗口内的最值,也就是局部的概念,所以可以维护一个单调递增的队列来查找窗口最小值,维护一个单调递减的队列来查找窗口最大值。并且注意要维护窗口的数据范围,也就是数据索引如果不在窗口中,就要出队。
定义一个双向队列,一个数对数组a(first是数组值,second是索引),窗口最初最右边的那个位置是k-2,之后就是每次往右边移动一个位置,所以我将a[0:k-2]先存入队列并且是维护窗口范围内的单调递增,也就是只要当前队尾值大于即将入队的值a[i]就将这个队尾值pop掉,之后再将a[i]入队尾,这个时候队列中的元素一定都是窗口内的值。之后将a[k-1:n-1]入队并且维护窗口范围内的单调递增,队尾值大于a[i]就pop掉,之后将a[i]入队尾,之后判断是否都是窗口内的值,队首元素一定是先入队的,所以一定是依次判断队首元素是否是窗口范围内的值,如果不是就pop掉。维护完毕后,当前队列中的队首一定是当前窗口范围内的最小值。
求最大值同上,维护一个窗口范围内的单调递减的队列。

代码实现:

#include<cstdio>
#include<queue>
#include<algorithm> 
using namespace std;


int n,k;
typedef pair<int,int> pairtype;
pairtype a[1000001];
deque<pairtype>q;
int themin[1000001],themax[1000001];


int main()
{
	scanf("%d%d",&n,&k);
	for(int i=0;i<n;i++)
	{
	    scanf("%d",&a[i].first);
	    a[i].second=i;
	}
	for(int i=0;i<k-1;i++)//先将所前k个数组元素维持一个单调递增队列 
	{
		while(!q.empty()&&q.back().first>a[i].first)
		    q.pop_back();
		q.push_back(a[i]);	
	}
	
	
	for(int i=k-1;i<n;i++)//窗口一定是从k-1位置开始移动的 
	{
		//printf("a[%d]:%d\n",i,a[i].first);
		while(!q.empty()&&q.back().first>a[i].first)//单调递增 
		    q.pop_back();
		q.push_back(a[i]);
		
		while(!q.empty()&&(i-q.front().second)>=k)//取出不在窗口内的数据 
		    q.pop_front();
		themin[i]=q.front().first;//队首元素会是最小的 	
	}
	
	q.clear() ;
	//while(!q.empty())
	  //  q.pop_back();//清空队列
	//if(q.empty())
	  //  printf("清空完毕!\n");
	
	for(int i=0;i<k-1;i++)
	{
		while(!q.empty()&&q.back().first<a[i].first)
		    q.pop_back();
		q.push_back(a[i]);
	}
	for(int i=k-1;i<n;i++)
	{
		while(!q.empty()&&q.back().first<a[i].first)
		    q.pop_back();
		q.push_back(a[i]);
		
		while(!q.empty()&&(i-q.front().second)>=k)
		    q.pop_front();
		themax[i]=q.front().first;
	}
	for(int i=k-1;i<n;i++)
	    printf("%d ",themin[i]);
	printf("\n");
	for(int i=k-1;i<n;i++)
	    printf("%d ",themax[i]);
	
	return 0;
}

D题总结:

这个题我是用的数对pair和队列,但其实可以考虑不使用数对,队列还是维护窗口范围内的单调递增但是存储的可以不是具体值而是数组索引,这样也可以做出来这道题。
另外,我一开始使用G++提交老是显示超时,后来问同学才知道用C++提交,顺便百度了一些关于G++和C++的资料,点击查看G++和C++的区别

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值