Week5-程序设计思维与实践

A - 最大矩形

题意:

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

思路:

对于每一个矩形高度,其可组成的最大矩形左右端点的范围即为以该矩形的高度为中心,向两边扩展直至遇到第一个高度小于它的矩形为止所组成的范围,暴力枚举的话时间复杂度为 O ( n 2 ) O(n^2) O(n2) 肯定会超时,故采用单调栈来进行优化,具体做法是维护一个单调递减栈,经两次单调栈处理即可得到以某一高度为中心的左右端点,然后计算以每一个高度为中心的矩形的最大面积,取最大值即可,经优化后时间复杂度降为 O ( n ) O(n) O(n)当然也可只进行一遍单调栈处理,因为对于单调递减栈来说,一个元素向左遍历的第一个比它小的数的位置就是把它插入单调栈时栈顶的元素(若栈为空则说明在该元素左边不存在比它小的数),同理,一个元素向右遍历的第一个比它小的数的位置就是使该元素出栈的元素的位置,以本题样例数据为例,在第五块矩形入栈前,栈内元素为 [ 1 , 4 , 5 ] [1,4,5] [1,4,5],故5的最左位置即为4所在位置,要使1入栈,则要弹出4和5,故4和5的最右位置即为第五块矩形所在位置,这样即可在每一次出栈时计算矩形面积,然后选取最大即为最终结果。

代码实现:

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
using namespace std;
typedef long long ll;

int n;
ll heights[100010];
ll st[100010];

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	while(cin>>n){
		if(n==0)
			break;
		for(int i=0;i<n;i++)
			cin>>heights[i];
		heights[n++]=0;
	    ll ans=0;
	    int top=0;
	    for(int i=0;i<n;i++){
	        while(top>0&&heights[st[top]]>heights[i]){
	            ll cur=st[top];
	            top--;
	            if(top==0)
	            	ans=max(ans,i*heights[cur]);
	            else
	            	ans=max(ans,(i-st[top]-1)*heights[cur]);
	        }
	        st[++top]=i;
	    }
	    cout<<ans<<endl;		
	}
    return 0;
}
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
using namespace std;
typedef long long ll;

int n;
ll heights[100010];
ll st[100010];

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	while(cin>>n){
		if(n==0)
			break;
		for(int i=0;i<n;i++)
			cin>>heights[i];
	    ll ans=0;
	    int top=0;
        int right[n+10],left[n+10];
        for(int i=0;i<n;i++){
            while(top>0&&heights[st[top]]>heights[i]){
                right[st[top]]=i;
                top--;
            }
            st[++top]=i;
        }
        int maxr=st[top]+1;
        while(top>0){
            right[st[top]]=maxr;
            top--;
        }
        for(int i=n-1;i>=0;i--){
            while(top>0&&heights[st[top]]>heights[i]){
                left[st[top]]=i;
                top--;
            }
            st[++top]=i;
        }	
        int maxl=st[top]-1;
        while(top>0){
            left[st[top]]=maxl;
            top--;
        } 
        for(int i=0;i<n;i++){
            ans=max(ans,(right[i]-left[i]-1)*heights[i]);
        }
        cout<<ans<<endl;		
	}
    return 0;
}

B - TT’s Magic Cat

题意:

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

思路:

由原数组构造差分数组,设原数组为 A A A,差分数组为 B B B,则构造方式如下:

  • B [ 1 ] = A [ 1 ] B[1] = A[1] B[1]=A[1]
  • B [ i ] = A [ i ] − A [ i − 1 ] B[i] = A[i] - A[i-1] B[i]=A[i]A[i1]

差分数组的特点为:

  • s u m B [ 1 — i ] = A [ i ] sum{B[1—i]} = A[i] sumB[1i]=A[i]
  • A [ L ] — A [ R ] 均 加 上 c 等 价 于 B [ L ] + = c , B [ R + 1 ] − = c A[L]—A[R]均加上c等价于B[L] += c,B[R+1] -= c A[L]A[R]cB[L]+=cB[R+1]=c

借助上述差分数据的构造方式及特点即可求解该题,时间复杂度为 O ( n + q ) O(n+q) O(n+q)

代码实现:

#include <iostream>
using namespace std;

int n,p,l,r,c;
long long a[200010],b[200010];

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin>>n>>p;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	b[1]=a[1];
	for(int i=1;i<=n;i++){
		b[i]=a[i]-a[i-1];
	}
	while(p--){
		cin>>l>>r>>c;
		b[l]+=c;
		b[r+1]-=c;
	}
	a[1]=b[1];
	cout<<a[1]<<" ";
	for(int i=2;i<=n;i++){
		a[i]=a[i-1]+b[i];
		cout<<a[i]<<" ";
	}
	cout<<endl;
	return 0;
}

C - 平衡字符串

题意:

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

思路:

这道题的关键就是搜索符合条件的最小区间,给定区间 [ L , R ] [L,R] [L,R], 首先讲述一下判断该区间是否合法的条件:

  • 记录不包含该区间的四种字符的个数
  • 通过替换字符使整体字符串满足平衡条件
  • 判断剩余的空闲位置(未被替换的字符的个数)是否为4的倍数,若空闲位置大于等于0且为4的倍数则满足条件;否则不满足。

合法的最小区间有多种搜索方法,这里只讲述一下尺取法二分法

  • 尺取法:初始设定两个指针 l = 0 , r = 0 l =0, r=0 l=0,r=0 分别表示区间的左右端点,若 [ l , r ] [l,r] [l,r]区间符合条件,则移动左指针,否则移动右指针,当 l > = n l>=n l>=n 时搜索结束,搜索过程中合法区间的最小长度即为最终结果。
  • 二分法:设置初始搜索的左右边界 l = 1 , r = n , m i d = ( l + r ) / 2 l=1,r=n,mid=(l+r)/2 l=1,r=n,mid=(l+r)/2,然后搜索所有区间长度为 m i d mid mid 的区间,若符合条件则设 r = m i d − 1 r=mid-1 r=mid1,否则设 l = m i d + 1 l=mid+1 l=mid+1,继续搜索,直到 l < = r l<=r l<=r

代码实现:

#include <iostream>
#include <string>
#include <map>
using namespace std;

string s;
map<char,int> p;

bool check(int l,int r){
	int tot=r-l+1;
	int sum[4];
	sum[0]=p['Q'];
	sum[1]=p['W'];
	sum[2]=p['E'];
	sum[3]=p['R'];
	int maxn=0;
	for(int i=0;i<4;i++)
		maxn=max(maxn,sum[i]);
	int free=tot;
	for(int i=0;i<4;i++){
		free-=(maxn-sum[i]);
	}
	if(free>=0&&free%4==0)
		return true;
	else
		return false;
}


int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin>>s;
	int n=s.size();
	for(int i=0;i<n;i++){
		p[s[i]]++;
	}
	if(p['Q']==n/4&&p['W']==p['Q']&&p['E']==p['Q']&&p['R']==p['Q']){
		cout<<0<<endl;
		return 0;
	}
	int l=0,r=0,ans=n;
	while(r<n){
		p[s[r]]--;
		while(check(l,r)){
			ans=min(ans,r-l+1);
			p[s[l++]]++;
		}
		r++;
	}
	cout<<ans<<endl;
	return 0;
}
#include <iostream>
#include <string>
#include <map>
using namespace std;

string s;
map<char,int> p;

bool Judge(int l,int r){
	int tot=r-l+1;
	int sum[4];
	sum[0]=p['Q'];
	sum[1]=p['W'];
	sum[2]=p['E'];
	sum[3]=p['R'];
	int maxn=0;
	for(int i=0;i<4;i++)
		maxn=max(maxn,sum[i]);
	int free=tot;
	for(int i=0;i<4;i++){
		free-=(maxn-sum[i]);
	}
	if(free>=0&&free%4==0)
		return true;
	else
		return false;
}

bool check(int len){
	for(int i=0;i<len;i++){
		p[s[i]]--;
	}
	int l=0,r=len-1;
	if(Judge(l,r))	
		return true;
	while(r<s.size()){
		p[s[l++]]++;
		p[s[++r]]--;
		if(Judge(l,r))
			return true;
	}
	return false;
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin>>s;
	int n=s.size();
	for(int i=0;i<n;i++){
		p[s[i]]++;
	}
	if(p['Q']==n/4&&p['W']==p['Q']&&p['E']==p['Q']&&p['R']==p['Q']){
		cout<<0<<endl;
		return 0;
	}
	int sum1=p['Q'],sum2=p['W'],sum3=p['E'],sum4=p['R'];
	int l=1,r=n,ans=n;
	while(l<=r){
		int mid=(l+r)>>1;
		if(check(mid)){
			ans=mid;
			r=mid-1;
		}
		else	l=mid+1;
		p['Q']=sum1;p['W']=sum2;p['E']=sum3;p['R']=sum4;
	}
	cout<<ans<<endl;
	return 0;
}

D - 滑动窗口

题意:

ZJM 有一个长度为 n 的数列和一个大小为 k 的窗口, 窗口可以在数列上来回移动。现在 ZJM 想知道在窗口从左往右滑的时候,每次窗口内数的最大值和最小值分别是多少。
在这里插入图片描述

思路:

滑动窗口内的最值是一个局部的最值,由于单调队列可以队首出队以及前面的元素一定比后面的元素先入队的性质,故本题可用单调队列来维护局部的单调性。利用单调队列的核心思想是去除冗余的状态,保持有用的状态即单调性,对于查找窗口内的最小值,可以维护一个单调递增队列,队尾入队(队尾元素大于当前入队元素时,队尾元素出队)、队头出队(队头元素不属于该窗口时),则队头元素即为窗口内的最小值,同理,最大值可以维护一个单调递减队列。

代码实现:

#include <iostream>
using namespace std;

struct num{
	int value;
	int id;
	num(){}
	num(int vv,int ii):value(vv),id(ii){} 
};

int n,k;
int a[1000010],minv[1000010],maxv[1000010];
num q1[1000010],q2[1000010];
int head1=0,tail1=-1,head2=0,tail2=-1;

int main()
{
	scanf("%d %d",&n,&k);
	for(int i=0;i<n;i++)
		scanf("%d",&a[i]);
	for(int i=0;i<k-1;i++){
		while(head1<=tail1&&q1[tail1].value>=a[i])
			tail1--;
		q1[++tail1]=num(a[i],i);
		while(head2<=tail2&&q2[tail2].value<=a[i])
			tail2--;
		q2[++tail2]=num(a[i],i);
	}
	int i=k-1,idx1=0,idx2=0;
	while(i<n){
		while(head1<=tail1&&q1[head1].id<i-k+1)
			head1++;
		while(head1<=tail1&&q1[tail1].value>=a[i])
			tail1--;
		q1[++tail1]=num(a[i],i);
		minv[idx1++]=q1[head1].value;
		while(head2<=tail2&&q2[head2].id<i-k+1)
			head2++;		
		while(head2<=tail2&&q2[tail2].value<=a[i])
			tail2--;
		q2[++tail2]=num(a[i],i);
		maxv[idx2++]=q2[head2].value;
		i++;
	}	
	for(int i=0;i<idx1;i++)
		printf("%d ",minv[i]);
	printf("\n");
	for(int i=0;i<idx2;i++)
		printf("%d ",maxv[i]);
	printf("\n");
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值