WEEK5程序设计作业

一、A - 最大矩形

1.题目

给一个直方图,求直方图中的最大矩形的面积。例如,下面这个图片中直方图的高度从左到右分别是2, 1, 4, 5, 1, 3, 3, 他们的宽都是1,其中最大的矩形是阴影部分。
在这里插入图片描述
输入
输入包含多组数据。

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

测试数据以0结尾。
输出
对于每组测试数据输出一行一个整数表示答案。
样例

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

2.解题思路

双重遍历会超时。

在这里采用单调栈。单调栈分为递增栈、递减栈、非递增栈、非递减栈。以递增栈为例,栈底到栈顶元素递增。

这里要求最大的矩形面积,只要求每一个小矩形左边、右边第一个小于该矩形高的矩形位置。就可以求出以该小矩形为中心的最大矩形面积。

在求每个小矩形右边第一个小于它的位置时,由于可能出现小矩形高相同的情况,所以这里采用单调非递减栈而非递增栈存储位置,也即栈底到栈顶是小于等于的关系。遍历所有的小矩形,当将该小矩形压入栈时,如果栈中的栈顶元素代表的小矩形值大于该小矩形,说明该小矩形为栈顶元素右边第一个小于它的值。不停判断栈顶元素与该矩形值的大小关系,直到满足单调非递减栈的条件,并将该小矩形压入栈。

当遍历到最后一个小矩形时,若栈中仍含有元素,就说明这些元素的右边都没有小于它的值,那么就将n作为第一个小于值的索引。求每个小矩形左边第一个小于它的值,只要将数组颠倒过来就可以。

注意呀,取值类型为long long。

3.c++代码

#include<stdio.h>
#include<stdlib.h> 
#include<iostream>
#include<stack> 
using namespace std;
long long a[100000];
int R[100000];//存放右边第一个小于a[i]的位置(从0开始) 
int L[100000];

int main()
{
	int n;
	//while(scanf("%d",&n)!=EOF&&n!=0)
	while(cin>>n&&n!=0)
	{
		for(int i=0;i<n;i++)
			//scanf("%d",&a[i]);
		cin>>a[i];
		//对于每个i,找到左右第一个小于a[i]的位置
		stack<int> thisS;//单调非递jian栈(栈底<=栈顶)
		//栈中存储的是位置 
		//求出右端第一个小于a[i]的位置
		for(int i=0;i<n;i++)
		{
			while(!thisS.empty()&&a[thisS.top()]>a[i])
			{
				R[thisS.top()]=i;
				thisS.pop();
			}
			thisS.push(i);
		}
		while(!thisS.empty())
		{
			R[thisS.top()]=n;//并非n-1,取边界线的右边第一格为最低 
			thisS.pop();
		}
		//找到每个元素左边第一个小于的位置
		//只要将数组反过来,或者维护一个单调非递减栈
		//但如果存在矩形的高存在相同值时,便会出现错误 
		//在此颠倒数组
		for(int i=n-1;i>=0;i--)
		{
			while(!thisS.empty()&&a[thisS.top()]>a[i])
			{
				L[thisS.top()]=i;
				thisS.pop();
			}
			thisS.push(i);
		} 
		while(!thisS.empty())
		{
			L[thisS.top()]=-1;
			thisS.pop();
		}
		//计算结果
		long long maxS=0,newS=0;
		for(int i=0;i<n;i++)
		{
			newS=a[i]*(R[i]-L[i]-1);
			if(newS>maxS)maxS=newS;
		}
		//printf("%d\n",maxS); 
		cout<<maxS<<endl;
	}
	return 0;
 } 

二、B - TT’s Magic Cat

1.题目

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?
输入
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.
输出
Print n integers a1,a2,…,an one per line, and ai should be equal to the final asset value of the i-th city.
样例

Input
4 2
-3 6 8 4
4 4 -2
3 3 1
Output
-3 6 9 2
Input
2 1
5 -2
1 2 4
Output
9 2
Input
1 2
0
1 1 -8
1 1 -6
Output
-14

2.解题思路

直接使用循环会超时,这里使用前缀和、差分。下面介绍这两个概念(摘自教学ppt)。
在这里插入图片描述在这里插入图片描述这里了将原数组转为差分数组,区间修改就可以转换为单点修改。即当[l,r]区间需要加c的时候,只要将B[l]+c;B[r+1]-c即可。最后输出每个值的时候只要输出前缀和。

注意取值类型。

3.c++代码

#include<iostream>
using namespace std;
int b[200050];//差分数组 

int main()
{
	long long n,q,x;
	cin>>n>>q;
	long long tmp=0;//记录当前数字的上一个数字 
	for(long long i=0;i<n;i++)
	{
		cin>>x;
		b[i]=x-tmp;
		tmp=x;
	}
	long long a1,a2,s;
	for(long long i=0;i<q;i++)
	{
		cin>>a1>>a2>>s; 
		b[a1-1]+=s;
		b[a2]-=s;
	}
	tmp=0;//前缀和 
	for(long long i=0;i<n;i++)
	{
		cout<<b[i]+tmp<<' ';
		tmp+=b[i];
	}
	return 0;
}

三、C - 平衡字符串

1.题目

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

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

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

如果 s 已经平衡则输出0。
输入
一行字符表示给定的字符串s
输出
一个整数表示答案
样例

Input
QWER
Output
0
Input
QQWE
Output
1
Input
QQQW
Output
2
Input
QQQQ
Output
3

note
1<=n<=10^5
n是4的倍数
字符串中仅包含字符 ‘Q’, ‘W’, ‘E’ 和 ‘R’.

2.解题思路

在这里使用尺取法。使用尺曲法需要找到题目的符合条件。

选取一段子串可以使字符串变成平衡字符串的条件是:如选取[l,r]为选取子串的区间,则统计区间外的四种字符的频数。取出最大的频数,并将其与其他字符频数之差的和求出。如果区间内的字母总数减去和可以被四整除,那么字符串可以变为平衡字符串。

尺取法实质为设置两个指针,让右指针从左到右遍历,如果遇到符合条件的情况,那么就让左指针向右遍历,并且不可以超过右指针。定义ans为符合条件的最短的区间长度。

注意每次求区间外字母的频数时不可以每次都从头到尾遍历一遍,这样会超时。所以设立一个map绑定字母和它的频数,当右指针从a字母移到字母b时,就让a字母的频数减一。当左指针从字母c移到字母d时就让字母c的频数加1。

3.c++代码

#include<iostream>
#include<algorithm>
#include<string.h>
#include<map>
using namespace std;

char c[100000];
int l=0,r=0;
map<char,int> thisM;
int s[4]={0};

int main()
{
	int check(int n,int *s);
	thisM['Q']=0;thisM['W']=1;thisM['E']=2;thisM['R']=3;
	
	cin>>c;
	int n=strlen(c);//字符个数 
	for(int i=0;i<n;i++)
		s[thisM[c[i]]]++;//计算每个字母的频数 
	if(s[0]==n/4&&s[1]==n/4&&s[2]==n/4&&s[3]==n/4)
	{
		cout<<0<<endl;
		return 0;
	}
	int ans=n,tmp;
	s[thisM[c[0]]]--;
	while(r<=n-1)
	{
		tmp=check(n,s);
		while(l<=r&&tmp>=0&&tmp%4==0)//符合条件 
		{
			ans=min(ans,r-l+1);
			s[thisM[c[l]]]++;l++;
			tmp=check(n,s);
		}
		//如果cut>=0且能被4整除,说明r-l+1为答案
		
		r++;
		s[thisM[c[r]]]--;
	}
	cout<<ans<<endl;
	return 0;
}

int check(int n,int *s)//返回当前区间的cut值 
{
	
	//计算区间外拥有最大频数的字母与其他字母的频数差之和
	int maxP=max(max(s[0],s[1]),max(s[2],s[3]));
	int sum=(maxP-s[0])+(maxP-s[1])+(maxP-s[2])+(maxP-s[3]);
	//得出区间内字母总数与sum的差
	int  cut=(r-l+1)-sum;
	return cut;
}

四、D - 滑动窗口滑动窗口

1.题目

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

输入
输入有两行。第一行两个整数n和k分别表示数列的长度和滑动窗口的大小,1<=k<=n<=1000000。第二行有n个整数表示ZJM的数列。
输出
输出有两行。第一行输出滑动窗口在从左到右的每个位置时,滑动窗口中的最小值。第二行是最大值。
样例

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

2.解题思路

这里求固定区间长度的最大值和最小值,不同于单调栈可以维护全局的最大最小值,当维护局部的最大最小值时,就可以使用单调队列。因为普通队列无法删除队尾元素,所以这里使用双端队列deque。

以计算最小值为例。维护单调递增双端队列(队首至队尾元素递增)存储数字在数组中的索引。

将数字从头到尾遍历,当尝试将一个数字压入队列时,若该数字索引为i,那么区间[i-k+1,i]内的最小值就可以求得。求解方法为,尝试将该数字加入队列,循环队列元素,若队列元素不为空,并且队尾元素代表的数字大于等于该数字,那么就将元素删掉。再将该数压入栈。判断队列的元素个数,如果元素个数超出题目中要求的区间长度,那么就将队首删掉。注意这里使用if就好,因为当压入一个数字时,最多只有一个队首元素需要pop。这时区间[i-k+1,i]的最小值就可以求得。

当求最大值时,只要维护单调递减双端队列。

3.c++代码

#include<iostream>
#include<deque>
#include<stdio.h>
#include<stdlib.h>
using namespace std;
const int MAX=1e6+50;
int a[MAX];//单调递增队列(队首<队尾) 
int minV[MAX];//存储最小值 
int maxV[MAX];//存储最大值 


int main()
{
	deque<int> thisQ;//单调递增双端队列,计算最小值
	deque<int> thisq;//单调递减双端队列,存储最大值 
	int n,k;
		scanf("%d %d",&n,&k);
	for(int i=0;i<n;i++)
	{
		scanf("%d",&a[i]);
		//最小值 
		while(!thisQ.empty()&&a[thisQ.back()]>=a[i])
			thisQ.pop_back();
		thisQ.push_back(i);
		if(thisQ.front()<i-k+1)
			thisQ.pop_front();
		if(i-k+1>=0)
			minV[i]=thisQ.front();//min的索引为k-1~n-1
		
		//最大值 
		while(!thisq.empty()&&a[thisq.back()]<=a[i])
			thisq.pop_back();
		thisq.push_back(i);
		if(thisq.front()<i-k+1&&i!=0)
			thisq.pop_front();
		if(i-k+1>=0)
			maxV[i]=thisq.front();
	}
	//队列存储a[i-k+1,i]的最小值在数组中的索引(从0开始) 
	for(int i=k-1;i<n;i++)
		//cout<<a[minV[i]]<<' ';
		printf("%d ",a[minV[i]]);
	printf("\n");
	for(int i=k-1;i<n;i++)
		//cout<<a[maxV[i]]<<' ';
		printf("%d ",a[maxV[i]]);
	return 0;
 } 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值