week5——区间类问题

问题A——最大矩形

问题描述

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

输入

输入包含多组数据。每组数据用一个整数n来表示直方图中小矩形的个数,你可以假定1 <= n <= 100000. 然后接下来n个整数h1, …, hn, 满足 0 <= hi <= 1000000000. 这些数字表示直方图中从左到右每个小矩形的高度,每个小矩形的宽度为1。 测试数据以0结尾。

输出

对于每组测试数据输出一行一个整数表示答案。

问题分析

为了找到最大矩形肯定需要考虑到所有的矩形情况,但是一个一个的列举时间耗费又有一些大。矩形面积的确定只需要决定长和宽即可。如果列举所有宽的情况,再从中选出最低的点,复杂度将近n的三次方,有些大。所以就采取,选取找到以每个点为高的最大矩形的左端点和右端点,可以去掉一些重复。
采用了两次单调增的栈,来计算左右端点。
先是从左往右依次插入各个点。当遇到比栈尾小的数的时候,就让队尾出栈,同时对以出栈的元素为高的矩形的左端点进行赋值——即将进栈的左边一位。所有元素都进过栈后,就再全部出栈,对以出栈的元素为高的左端点进行赋值——最右边的点。
然后再从右往左再来一次,道理同上,给右端点赋值。
这个时候我们已经找好了左右端点,只要再次挨个列举顶点,和这个顶点为高的左右端点计算得到的矩形面积,然后比较保留最大的矩形面积即可。

代码
#include<iostream>
using namespace std;
int h[100001];
long long max_area=0;
int s[100000];//s栈的实现 
int ll[100001];
int rr[100001];
int main()
{
	int n;
	cin>>n;
	while(n!=0)
	{
		for(int i=1;i<=n;i++)
		{
			cin>>h[i];
		}
		int tail=0;
		int j=1;
		int left1,left2,right1,right2;
		s[0]=1;
		while(j<=n)
		{
			if(h[s[tail]]>h[j])
			{
				left1=s[tail];
				while(h[s[tail]]>h[j]&&tail!=-1)
				{
					rr[s[tail]]=left1;
					//cout<<"   第"<<s[tail]<<" 点的右边是"<<left1<<endl;
					tail--;
				}
			}
			
			tail++;
			s[tail]=j;
			j++;
		}
		left1=s[tail];
		while(tail!=-1)
				{
					rr[s[tail]]=left1;
					//cout<<"   11第"<<s[tail]<<" 点的右边是"<<left1<<endl;
					tail--;
				}
		tail=0;
		s[0]=n;
		j=n;
		while(j>=1)
		{
			if(h[s[tail]]>h[j])
			{
				right1=s[tail];	
				while(h[s[tail]]>h[j]&&tail!=-1)
				{
					ll[s[tail]]=right1;
					//cout<<"   第"<<s[tail]<<" 点的左边是"<<right1<<endl;
					tail--;	
				}			
			}			
			tail++;
			s[tail]=j;
			j--;
		}
		right1=s[tail];		
		while(tail!=-1)
		{
			ll[s[tail]]=right1;
			//cout<<"   11第"<<s[tail]<<" 点的左边是"<<right1<<endl;
			tail--;
					
		}
		long long shu;
		max_area=0;
		for(int i=1;i<=n;i++)
		{
			shu=h[i];
			shu=shu*(rr[i]-ll[i]+1);
			
			if(shu>=max_area)
			{
				max_area=shu;
				//cout<<" r="<<rr[i]<<"   l="<<ll[i]<<"   shu="<<shu<<endl;
			}
			//else
			//cout<<"   xiao:   r="<<rr[i]<<"   l="<<ll[i]<<"   shu="<<shu<<endl;
		}
		cout<<max_area<<endl;
		cin>>n;
	}
	
	return 0;
} 
遇到的问题

注意数据的大小,10e10是已经超过int的范围大小了。同时longlong=int*int这个没法解决溢出问题。在赋值前已经溢出数据了。
然后就是对于这个算法不大熟悉,前期未能理解其中的含义,导致胡乱试了很多种乱七八糟的写法。

问题B——前缀和

问题描述

多亏了上周大家的帮助,它终于得到了一只可爱的猫。但没想到这是一只神奇的猫。
有一天,神奇的猫决定调查TT的能力,给他一个问题。即从世界地图中选择n个城市,a[i]表示第i个城市拥有的资产价值。
然后,这只神奇的猫将执行几项操作。每轮选择[l,r]区间内的城市,并将其资产价值增加c。最后,需要给出q操作后各城市的资产价值。
你能帮我找到答案吗?

输入

第一行包含两个整数n,问(1≤n, q≤2⋅105)——城市和操作的数量。
第二行包含序列a的元素:整数a1,a2,…,一个ai (106−≤≤106)。
接下来是q行,每一行代表一个操作。第i行包含3个整数l,r和c(1≤l≤r≤n,−105≤c≤105)用于第i个操作。

输出

打印n个整数a1,a2,…,每行一个,ai应该等于第i个城市的最终资产价值。

问题分析

首先,如果像以前那样一个个的对a数组的值进行修改,时间是肯定会超的,因为这个工作其实挺重复的,但是一个个加起来耗时也很巨大。
采用前缀和的方法,用前缀来表示,l,r上加上q的这个操作。
所需的就是,先计算所有的前缀,用b[i]=a[i]-a[i-1]来赋值。之后每个a[i]可以用b[0]+b[1]+…b[i]来表示
所以,要使得l,r的区间上面都加上q,就在l,r区间的所有a[i]的组成式都包含的内容上加上q;很明显这个内容就是b[l]。当然后面的也会跟着改变,就需要再把后面的值给改回来,同理,在后面不改变化的a[i]让它再减去q,即在b[r+1]的地方减q。
多次操作完后,挨个输出即可。

代码
#include<stdio.h>
int a[1000002];
int b[1000002];
int main()
{
	int n,q;
	int l,r,c;
	long long shu;
	//cin>>n>>q;
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		//printf("%d\n",a[i]); 
		if(i==0)
			b[0]=a[0];
		else
			b[i]=a[i]-a[i-1];
		//printf("      b=%d\n",b[i]);
	}
	for(int i=0;i<q;i++)
	{
		scanf("%d%d%d",&l,&r,&c);
		b[l]+=c;
		b[r+1]-=c;
	}
	shu=0;
	for(int i=1;i<=n;i++)
	{
		//cin>>a[i];
		shu=shu+b[i];
		printf("%lld",shu);
		if(i!=n)
			printf(" ");
		else
			printf("\n");
	}
	return 0;
} 
遇到的问题

这道题的算法倒是很容易理解,但是吧,我在这里又再一次忘记看数据大小,直接交了,我悔过。

问题C——平衡字符串

问题描述

一个长度为 n 的字符串 s,其中仅包含 ‘Q’, ‘W’, ‘E’, ‘R’ 四种字符。

如果四种字符在字符串中出现次数均为 n/4,则其为一个平衡字符串。

现可以将 s 中连续的一段子串替换成相同长度的只包含那四个字符的任意字符串,使其变为一个平衡字符串,问替换子串的最小长度?

如果 s 已经平衡则输出0。

输入

一行字符表示给定的字符串s

输出

一个整数表示答案,0,不变;其他正整数,表示替换字串最小长度

问题分析

因为这个替换的字符串是连续的,所以只需要一直往右扩大区间,当区间符合要求时,就再把左端点往左移,看看缩小能不能依旧符合区间要求,一旦不满足就又继续往右扩展开来。
要最开始先计算QWER的个数,然后先比较没有任何交换时是不是已经满足了情况。
然后就开始遍历,判断是否符合区间要求的时候,记录QWER中不在被选择空间的数目,包含的空间的大小为sum,然后看看sum个任意字符(可代替任何字符)是否能够使在外面的QWER达到一样的数目(不够就加)(该区间的值无所谓可以随便乱改的那种),如果可以,就看剩下的数目是否可以被分成QWER四个字符。可以即区间符合。
区间从左移到了右边后,中间记录好最小的值,即可。

代码
#include<stdio.h>

char c[100000];
int max(int a,int b,int c,int d)
{
	int ans=a;
	if(ans<b) ans=b;
	if(ans<c) ans=c;
	if(ans<d) ans=d;
	return ans;
}
int main()
{
	int i=0;
	int sum_Q=0,sum_W=0,sum_E=0,sum_R=0;
	
	scanf("%c",&c[0]);
	while(c[i]!='\n')
	{
		
		if(c[i]=='Q')
			sum_Q++;
		else if(c[i]=='W')
			sum_W++;
		else if(c[i]=='E')
			sum_E++;
		else if(c[i]=='R')
			sum_R++;
		i++;
		scanf("%c",&c[i]);
		
		
	}
	if(sum_Q==i/4&&sum_R==sum_Q&&sum_W==sum_Q&&sum_E==sum_Q)
	{
		printf("%d\n",0);
		return 0;
	}
	else
	{
		int left=0,right=0;//指向一个选择的区间的左端点和右端点 
		int min_num=i;//最小改动 
		int max_num;
		int free;
		int sum_w=sum_W,sum_e=sum_E,sum_r=sum_R,sum_q=sum_Q;
		if(c[right]=='W')
			sum_w--;
		else if(c[right]=='Q')
			sum_q--;
		else if(c[right]=='E')
			sum_e--;
		else if(c[right]=='R')
			sum_r--;
		while(right<i)
		{
			max_num=max(sum_q,sum_w,sum_e,sum_r);
			free=right-left+1-(max_num-sum_q)-(max_num-sum_w)-(max_num-sum_e)-(max_num-sum_r);
			if(free>=0&&free%4==0)
			{
				if(min_num>right-left+1)
					min_num=right-left+1;
				if(c[left]=='W')
					sum_w++;
				else if(c[left]=='Q')
					sum_q++;
				else if(c[left]=='E')
					sum_e++;
				else if(c[left]=='R')
					sum_r++;
				left=left+1;
			}
			else
			{
				right++;
				if(c[right]=='W')
					sum_w--;
				else if(c[right]=='Q')
					sum_q--;
				else if(c[right]=='E')
					sum_e--;
				else if(c[right]=='R')
					sum_r--;
			}
		}
		printf("%d\n",min_num);
	}
	
	return 0;
}
遇到的问题

最开始对于区间符合条件的判断十分不能够理解,只是感觉很复杂,做的时候也是似懂非懂,没想到需要我写出来的时候,我才慢慢摸索出来了。

问题D——滑动窗口

问题描述

ZJM 有一个长度为 n 的数列和一个大小为 k 的窗口, 窗口可以在数列上来回移动. 现在 ZJM 想知道在窗口从左往右滑的时候,每次窗口内数的最大值和最小值分别是多少. 例如:
数列是 [1 3 -1 -3 5 3 6 7], 其中 k 等于 3.
Window position Minimum value Maximum value
[1 3 -1] -3 5 3 6 7 -1 3
1 [3 -1 -3] 5 3 6 7 -3 3
1 3 [-1 -3 5] 3 6 7 -3 5
1 3 -1 [-3 5 3] 6 7 -3 5
1 3 -1 -3 [5 3 6] 7 3 6
1 3 -1 -3 5 [3 6 7] 3 7

输入

输入有两行。第一行两个整数n和k分别表示数列的长度和滑动窗口的大小,1<=k<=n<=1000000。第二行有n个整数表示ZJM的数列。

输出

输出有两行。第一行输出滑动窗口在从左到右的每个位置时,滑动窗口中的最小值。第二行是最大值。

问题分析

采用单调队列的方法挨个窗口计算输出。
用单调增队列来计算最小值,队空或者队尾小于要加进来的数,直接将数加在队尾。队尾大于要加进来的数,就使队尾元素出队,一直重复,直到满足该数的加入条件。同时在该k大小窗口的数都进来后,直接输出队头的元素。在窗口移动后又把不属于该窗口的数移出队列。(我只移动了队头)因为我觉得只有这个比较重要,比队头后面的数一定是比队头晚加入队列的,也就是一定会在k窗口中(因为移出及时)
随后再用单调减队列,计算最大值,原理大概同上,队空和队尾大于加进来的数,直接加入,否则,出队尾直到情况运行加入该数。注意输出的时间和队头的出队即可。

代码
#include<stdio.h>
int shu[1000000];
int q[1000000];
int main()
{
	int n,k;
	int num;
	scanf("%d%d",&n,&k);
	for(int i=0;i<n;i++)
	{
		scanf("%d",&shu[i]);
	}
	int head=0,tail=0;
	for(int i=0;i<k;i++)
	{
		if(tail<=head)//空 
		{
			//printf("kong的    tail=%d  head=%d %d_ %d\n",tail,head,i,shu[i]);
			q[tail]=i;
			tail++;
		}
		else if(shu[q[tail-1]]<=shu[i])
		{
			//printf("队尾的数:  %d _ 数字大小%d    比较的数:%d_   大小%d\n",q[tail-1],shu[q[tail-1]],i,shu[i]);
			q[tail]=i;
			tail++;
		}
		else
		{
			//printf(" 需要出队的情况:     %d_ %d\n",i,shu[i]);
			while(shu[q[tail-1]]>shu[i]&&tail>head)
			{
				//printf("shu=%d     q=%d    \n",shu[q[tail-1]],q[tail-1]);
				tail--;
				//printf(" 减一过后  shu =%d  tail=%d \n",shu[q[tail-1]],tail);
			}
				
			q[tail]=i;
			tail++;
			//printf(" 修改完后:   tail=%d _ 队尾 %d     数:%d_ %d\n",tail,q[tail-1],shu[q[tail-1]],i,shu[i]);
		}
	}
	for(int i=k;i<n;i++)
	{
		num=shu[q[head]];
		printf("%d ",num);
		if(q[head]<=i-k&&q[head]!=n-k)
		{
				head++;
		}
		
			
		if(tail<=head)//空 
		{
			
			q[tail]=i;
			tail++;
			
		}
		else if(shu[q[tail-1]]<=shu[i])
		{
			//printf("%d _ %d  %d_ %d\n",q[tail-1],shu[q[tail-1]],i,shu[i]);
			q[tail]=i;
			tail++;
		}
		else 
		{
			while(shu[q[tail-1]]>shu[i]&&tail>head)
			{
				//printf(" 111  %d  tail=%d \n",shu[q[tail-1]],q[tail]);
				tail--;
				//printf(" 122  %d  tail=%d \n",shu[q[tail-1]],tail);
			}
				
			q[tail]=i;
			tail++;
			//printf(" 修改完后:   tail=%d _ %d  %d_ %d\n",tail,q[tail-1],shu[q[tail-1]],i,shu[i]);
			
		}
	}
	num=shu[q[head]];
	printf("%d\n",num);
	//printf("\n");
	head=0;
	tail=0;
	for(int i=0;i<k;i++)
	{
		if(tail<=head)//空 
		{
			//printf("kong的    tail=%d  head=%d %d_ %d\n",tail,head,i,shu[i]);
			q[tail]=i;
			tail++;
		}
		else if(shu[q[tail-1]]>=shu[i])
		{
			//printf("队尾的数:  %d _ 数字大小%d    比较的数:%d_   大小%d\n",q[tail-1],shu[q[tail-1]],i,shu[i]);
			q[tail]=i;
			tail++;
		}
		else
		{
			//printf(" 需要出队的情况:     %d_ %d\n",i,shu[i]);
			while(shu[q[tail-1]]<shu[i]&&tail>head)
			{
				//printf("shu=%d     q=%d    \n",shu[q[tail-1]],q[tail-1]);
				tail--;
				//printf(" 减一过后  shu =%d  tail=%d \n",shu[q[tail-1]],tail);
			}
				
			q[tail]=i;
			tail++;
			//printf(" 修改完后:   tail=%d _ 队尾 %d     数:%d_ %d\n",tail,q[tail-1],shu[q[tail-1]],i,shu[i]);
		}
	}
	for(int i=k;i<n;i++)
	{
		num=shu[q[head]];
		printf("%d ",num);
		if(q[head]<=i-k&&q[head]!=n-k)
		{
				head++;
		}
		
			
		if(tail<=head)//空 
		{
			
			q[tail]=i;
			tail++;
			
		}
		else if(shu[q[tail-1]]>=shu[i])
		{
			//printf("%d _ %d  %d_ %d\n",q[tail-1],shu[q[tail-1]],i,shu[i]);
			q[tail]=i;
			tail++;
		}
		else 
		{
			while(shu[q[tail-1]]<shu[i]&&tail>head)
			{
				//printf(" 111  %d  tail=%d \n",shu[q[tail-1]],q[tail]);
				tail--;
				//printf(" 122  %d  tail=%d \n",shu[q[tail-1]],tail);
			}
				
			q[tail]=i;
			tail++;
			//printf(" 修改完后:   tail=%d _ %d  %d_ %d\n",tail,q[tail-1],shu[q[tail-1]],i,shu[i]);
			
		}
	}
	num=shu[q[head]];
	printf("%d\n",num);

	return 0;
}
遇到的问题

1.为了不超时使用的q数组来代表队列,但是在对其进行操作时,不能够很准确又快速的写出其每个操作的下标。
2.不知道为啥G++就是超时,这一点现在还没有解决,我觉得我已经把平时容易弄慢的地方都修改了很多了,但是还是超时。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值