SDU程序设计思维与实践作业Week5

A 最大矩形

题目

题目
Input&&Output:
Input&&Output
Sample:

#Input:
7 2 1 4 5 1 3 3
4 1000 1000 1000 1000
0

#Output:
8
4000

题解

1.根据题目,我们发现每个矩形条对应的最大矩形,就是它左右可以延伸到的最远位置
而最远位置就是高度(y值)大于等于它,因此我们发现对于每个矩形条我们实际上是
找它左边第一个大于等于它的位置以及右边最后一个大于等于它的位置。
2.我们直接将每个矩形条看作它最大的矩形,那么我们会发现它右边界一定大于它
相邻的右边第一个矩形(不然就不是最大的,边界特判  也就是说我们只要能找到右
边第一个比它小的值就可以了),我们可以认为,矩形高度满足一定的递增规律,我
们只要记录这组递增的值对应的下标,就可以判断出每个矩形的右边界。
3.因此本题我们利用一种特别的数据结构 单调栈(出栈递增或递减)
4.由刚刚的分析 我们发现右边界做一遍递增栈,而右边界再做一次递减栈就可以了
5.但是实际上我们做一遍递增栈就可以了,我们发现左边进入的第一个比它大的是左
边界,那么出去的最后一个大于等于的一定是它的左边界(考虑过程中弹出的 如果过
程中被弹出那么我们只需要看栈中它前面哪个位置 如果与它相等那么它的左边界等于
它前面的否则他就是自己的左边界。——类似路径给出)
6.对栈内剩余元素的处理 此时栈内元素左边界均为数组大小,同时可以判断左边界

PS:单调栈常用来处理全局下的最值、边界等

C++代码

#include<iostream>

using namespace std;
const int MAXN = 1e6 +500;
long long ele[MAXN],R[MAXN],L[MAXN],st[MAXN];
int n;

init (int num){
	for(int i = 0;i<num+5;i++){
		ele[i] = 0;R[i] = 0;L[i] = 0;st[i] = 0;
	}
}
void R2L(int r){
	if(r == 0) L[st[r]] = 0;
	if(r>=1 && ele[st[r]] > ele[st[r-1]] && L[st[r]] == 0) L[st[r]] = st[r];
	if(r>=1 && ele[st[r]] == ele[st[r-1]])L[st[r]] = st[r-1];
}
void solve(){
	int l = 1,r = 0;//模拟栈 r表示栈顶  栈内存ele元素下标 
	for(int i = 0;i<n;i++){
		if(r<l){st[r++] = i;continue;}
		while(r>=l&&ele[st[r]]>ele[i]){//改变栈顶以及右边第一个比当前元素小的 
			R2L(r);
			L[i] = st[r];
			R[st[r--]] = i-1;
		}
		st[++r] = i;
	}
	while(r!=0){
		R2L(r);
		R[st[r--]] = n-1;
	}
}
int main(){
	long long ans = 0;
	while(1){
		cin>>n;
		if(n == 0) break;
		ans = 0;
		init(n);
		for(int i = 0;i<n;i++){
			cin>>ele[i];
		}
		solve();
		for(int i = 0;i<n;i++){
			L[i] = L[L[i]];//L类似路径给出 
			if(ans<ele[i]*(R[i]-L[i]+1)) ans = ele[i]*(R[i]-L[i]+1);
		}
		cout<<ans<<endl;
	}
	return 0;
} 

TT’s Magic Cat

题目

题目题意:
有一组个数为n的数组,有q组区间对应数组的区间,分别在qi区间加上数c,那么最终这个数组会变成什么样子
Input&&Output:
Input&&Output
Sample:
Sample

题解

1.连续区间内的增量问题通常使用差分数组和前缀和共同解决
2.差分特点若B为A的差分数组则在A的[m,n]区间内各加k那么对于B来说只影响了两个
数
3.前缀和与差分,若B为A的差分数组那么B的前缀和就为A
差分数组:B[0] = A[0] B[i] = A[i]-A[i-1](i>0)

C++代码

#include<iostream>

using namespace std;
const int MAXN = 1e6+500;
long long A[MAXN],B[MAXN],Sumb[MAXN];
int n,q,c,l,r;

void solve(){
	for(int i = 0;i<n;i++){
		cin>>A[i];
		if(i == 0) B[i] = A[i];
		else B[i] = A[i] - A[i-1];
	}
	for(int i = 0;i<q;i++){
		cin>>l>>r>>c;
		B[l-1]+=c;
		if(r<n) B[r+1]-=c;
 	}
}
int main(){
	cin>>n>>q;
	solve();
 	for(int i = 0;i<n;i++){
 		if(i == 0) Sumb[i] = B[i];
		else Sumb[i] = Sumb[i-1]+B[i];
		cout<<Sumb[i]<<" ";
	}
	cout<<endl;
	return 0;
} 

C 平衡字符串

题目

题目
Input&&Output:
Input&&Output
Sample:

#Input
QWER
#Output
0

#Input
QQWE
#Output
1

#Input
QQQW
#Output
2

#Input
QQQQ
#Output
3

题解

1.分析题可知我们可以将本题转化为这样一个问题,A,B,C为三个区间字符数组,
其中B中内容未知A,C已知则可否通过填写B使得A、B、C中的字符达到某种平衡
如果可以B最短为多少
2.根据上述分析我们首先可以构造区间 [l,r]那么原数组被分为[0,l-1],[l,r],[r+1,n]
l,r可变,为了保证遍历的完整性 我们令 l=r=0,然后开始遍历整个区间。
3.遍历准则:
1)满足条件的l,r,由于本题要求QWER四个字符数量相同,那么对于区间[l,r]来说我们
首先需要将[0,l-1][r+1,n]中不平衡的字符补齐,根据最小原则也就是将所有字符数
量补充为max(Q,W,E,R),如果剩余元素为4的倍数那么满足
2)如何完整遍历:为了完整遍历,我们首先应该令l不变r自增直至满足条件,此时
自增l缩小区间,若缩小后不满足继续自增r,直至遍历完整个数组

总结:
对于这种判断区间内连续的事情,并且LR有明确移动方向我们可以考虑这种方法——
尺取法

C++代码

/*
尺取法:
常用于判断连续区间内的事情
且L R移动有明确方向 
*/ 
#include<stdio.h>
#include<map>
using namespace std;
const int MAXN = 1e6+500;
char word[MAXN];
long long SUM[4]; 
int MAX = 0,l = 0,r = -1,total;
map<char,int> m;
int solve(int n){
	long long ans = n;
	while(1){
		MAX = max(max(SUM[0],SUM[1]),max(SUM[2],SUM[3]));
		total = r-l+1;
		long long mid = total - (4*MAX - SUM[0] - SUM[1] - SUM[2] - SUM[3]);
		//printf("%d %d %d %d %d %d %d\n",SUM[0],SUM[1],SUM[2],SUM[3],mid,r,r,l);
		if((mid < 0||mid % 4 != 0)&&r!=n-1){
			r++;
			SUM[m[word[r]]]--;
		}else{
			if(ans > total && mid>=0 && mid % 4 == 0) ans = total;
			//printf("%d %d %d\n",r,l,total);
			if(l>r || l == n-1) break;
			SUM[m[word[l]]]++;
			l++;
		}
	}
	return ans;
}
int main(){
	m['Q'] = 0;m['W'] = 1;m['E'] = 2;m['R'] = 3;
	int i = 0;
	char c;
	while(1){
		scanf("%c",&c);
		if(c == '\n') break;
		word[i] = c;
		SUM[m[c]]++;
		i++;
	}
	printf("%d\n",solve(i));
	return 0;
}

D 滑动窗口

题目

题目
Input&&Output:
Input&&Output
Sample:

#Input:
8 3
1 3 -1 -3 5 3 6 7

#Output:
-1 -3 -3 -3 3 3
3 3 5 5 6 7

题解

1.考虑一个窗口 [l,r] length = k(以求最大值为例)
2.那么这个窗口内的最大值我们可以视作 max(e[i属于[l,r-1]],e[r]) 可以发现这
是一个局部最大值,而且根据甘冈类似于递归推max我们可以发现当 e[l]进入时假设
我们维持了窗口的大小,那么他前面比他小的数都可以舍去了然后取出剩余的他之前
比它大的数的最大值与他作比较,取更大的
3.具体实现,为了维持窗口大小我们发现,当 r+1进入时 我们需要丢掉 l (如果有
元素),然后我们需要维持剩余元素的大小关系,由于是最大值我们可以想到维持
一个递增序列。
4.数据结构的选择——由于我们需要从一端加另一端减 因此应该队列是可以实现的
5.因此维持一个递增队列就好了(递增队列——队列首最大(l) 队列尾最小(r))
6.最小值构建一个递减队列就可以了

PS:对于这种局部性问题,并且跟滑动窗口类似的问题经常会用到单调队列

C++代码

#include<iostream>

using namespace std;
const long long MAXN = 1e6+500;
int que[MAXN],q[MAXN],que2[MAXN];//单调递增队列 队列存下标 队列首对应q元素最小 
long long n,k,l = 0,r = -1;//r队列尾,l队列首 
int MAX[MAXN],MIN[MAXN];
void solve_min(){
	l = 0,r = -1;
	for(int i = 0;i<n;i++){
		if(r<l) que[++r] = i;
		else{
			while(i - que[l]>=k&&r>=l) l++;//去除超框数 
			if(r<l) que[++r] = i;
			else{
				while(q[que[r]]>q[i] && r>=l) r--;
				que[++r] = i;
			}
		}
		MIN[i] = q[que[l]];
	} 
}

void solve_max(){
	l = 0;r = -1;
	for(int i = 0;i<n;i++){
		if(r<l) que[++r] = i;
		else{
			while(i - que[l]>=k&&r>=l) l++;
			if(r<l) que[++r] = i;
			else{
				while(q[que[r]]<q[i] && r>=l) r--;
				que[++r] = i;
			}
		}
		MAX[i] = q[que[l]];
	} 
}

int main(){
	std::ios::sync_with_stdio(false);
	cin>>n>>k;
	for(int i = 0;i<n;i++){
		cin>>q[i];
	}
	solve_max();
	solve_min();
	for(int i = k-1;i<n;i++){
		cout<<MIN[i]<<" ";
	}
	cout<<endl;
	for(int i = k-1;i<n;i++){
		cout<<MAX[i]<<" ";
	}
	cout<<endl;
	return 0;
}
发布了7 篇原创文章 · 获赞 3 · 访问量 440

猜你喜欢

转载自blog.csdn.net/TIM__one/article/details/105032097
今日推荐