程序设计思维与实践 Week5 作业 (3/4/数据班)
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
问题分析
每个矩形有两个参数,其一是它的位置(序号),其二是它的高度。对于每个矩形,可将其向左右“扩展”,前提是相邻的矩形高度大于等于它本身。这样,能确定最左边和最右边的范围,进而求出某个矩形所在的最大矩形的面积,最后进行比较。
具体实施起来,使用单调栈。以每个矩形右边能达到的最远处为例。设置一个栈,遍历从它开始的矩形,如果栈不空,栈顶位置的矩形的高度大于当前矩形的高度,则弹出,这样,栈顶始终是这个元素向右延申的最远处。
#include <iostream>
#include <stack>
using namespace std;
int h[100010];
int l[100010];
int r[100010];
int n;
long long ans=0;
int main() {
while (cin>>n&&n!=0) {
ans=0;
for (int i=1; i<=n; i++) {
cin>>h[i];
l[i]=0;
r[i]=n+1;
}
stack<pair<int, int> > s;
for (int i=1; i<=n; i++) {
while (!s.empty()&&s.top().second>h[i]) {
r[s.top().first]=i;
s.pop();
}
s.push(pair<int, int>(i,h[i]));
}
stack<pair<int, int> > s2;
for (int i=n; i>=1; i--) {
while (!s.empty()&&s.top().second>h[i]) {
l[s.top().first]=i;
s.pop();
}
s.push(pair<int, int>(i,h[i]));
}
for (int i=1;i<=n; i++) {
if((long long)(r[i]-l[i]-1)*h[i]>ans)
ans=(long long)(r[i]-l[i]-1)*h[i];
}
cout<<ans<<endl;
}
return 0;
}
B - TT's Magic Cat
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.
Examples
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
问题分析
题目的意思是,给出所有的城市的资产,每次让某个范围的城市的资产都变化一个值,多次操作后,给出各个城市的资产
如果遍历每一个城市,耗时将不可接受。使用差分的方法,新建一个差分数组,映射关系是,b[1]=a[1],b[n]=a[n]-an-1。如要[l, r]范围内的资产变更x,只需要差分数组的第l个变更x,第r+1个变更-x。最后可用累加求和的方法,求得每个城市的变更后的资产。
#include<stdio.h>
typedef long long ll;
ll a[200005];
ll b[200005];
int main()
{
ll l,r,n,q,c;
scanf("%lld %lld",&n,&q);
for(ll i =1 ; i<=n ;i++){
scanf("%lld",&a[i]);
}
for(ll i=1; i<=n; i++){
b[i]=a[i]-a[i-1];
}
for (ll i=1; i <= q; i++)
{
scanf("%lld %lld %lld",&l,&r,&c);
b[l] +=c;
b[r+1] -= c;
}
printf("%lld",b[1]);
long long sum = b[1];
for(ll i=2;i<=n;i++)
{
sum +=b[i];
printf(" %lld",sum);
}
printf("
");
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
Input
QQQW
Output
2
Input
QQQQ
Output
3
Note
1<=n<=10^5, n是4的倍数, 字符串中仅包含字符 'Q', 'W', 'E' 和 'R'.
问题分析
这是一道尺取法的题目。
首先统计每个字符的数目,判断是否已经满足条件。
如果不平衡,通过移动区间的方法,使得某个通过改变某个区间内的元素,整个字符串达到平衡,确定最小的区间。代码中有注释。
#include<bits/stdc++.h>
using namespace std;
int main() {
string s;
cin >> s;
vector<char> chars;//{'Q','W','E','R'};
chars.push_back('Q');//为四个字符编号,便于统计数目
chars.push_back('W');
chars.push_back('E');
chars.push_back('R');
map<char, int> cnt;
bool balance = true;
for (auto ch : s)//统计每个字符出现的次数
++cnt[ch];
int len = s.size();
int n = len / 4;
for (auto ch : chars)
{
cnt[ch] -= n;
if (cnt[ch] > 0)
balance = false;
}
if (balance == true) {
cout << 0 << endl;
return 0;
}
int left = 0, right = 0, num = len;
while (left <= right && right < len)
{
bool find = true;
--cnt[s[right]];//假设所选区间最右边的元素被替换
while (find)
{
for (auto ch : chars)//遍历字符串
{
if (cnt[ch] > 0)//尚且不平衡
{
find = false;//中断循环
break;
}
}
if (find == true)//如果通过改变区间最右边元素平衡
{
num = min(num, right - left + 1);
++cnt[s[left++]];//左端点向右移动
}
}
++right;//右端点向右移动
}
cout << num << endl;
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
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
问题分析
用数组模拟双端队列。对于求窗口最小值的情况,如果队列非空,则弹出所有大于等于当前元素的数,对空则入队,这样储存的是当前窗口的最小元素。每次检查第队首是不是过期,过期则弹出
另一种情况同理讨论。可以用stl,仍然不会超时,但是,有一点需要注意,pop_back()的时候,含等于号,如果避寒等于号,会出错。
#include<stdio.h>
int arr[1000005];
int dq[1000005];
int ans1[1000005];
int ans2[1000005];
int main()
{
int n,k;
scanf("%d%d",&n,&k);
for(int i=0;i<n;++i){
scanf("%d",&arr[i]);
}
int l=0,r=-1;
for(int i=0;i<n;++i)
{
while(r>=l&&arr[dq[r]]>=arr[i])//不空,则从尾到头,弹出所有比当前元素大的
{
r--;
}
dq[++r]=i;//当前元素序号入队
if(i>=k-1&&i-dq[l]>=k) //队首过期
{
l++;
}
ans1[i]=arr[dq[l]];
}
l=0,r=-1;
for(int i=0;i<n;++i)
{
while(r>=l&&arr[dq[r]]<=arr[i])
{
r--;
}
dq[++r]=i;
if(i>=k-1&&i-dq[l]>=k)
{
l++;
}
ans2[i]=arr[dq[l]];
}
printf("%d",ans1[k-1]);
for(int i=k;i<n;++i){
printf(" %d",ans1[i]);
}
printf("
%d",ans2[k-1]);
for(int i=k;i<n;++i){
printf(" %d",ans2[i]);
}
printf("
");
return 0;
}