程序设计思维与实践 Week5 作业
A - 最大矩形
题意:
给一个直方图,求直方图中的最大矩形的面积。
思路:
要求最大矩形的面积,关键是对于每一个小的矩形,都要找到左右两端比他矮的所有矩形,这样能够求出每一个矩形高在直方图中最大矩形面积,之后再对这些矩形面积进行比较,选出最大矩形即可。
所以现在关键在于如何对于每一个节点,向右找到第一个比他小的节点,向左找到第一个比他小的节点。单调栈就是为这个而生的。
首先将栈中a[0]和a[n+1]都设置为最小值,这样能让每个节点都能找到比他小的左右节点。
采用单调非减栈。对于节点i,有以下两种情况:
- 栈顶元素>a[i],不满足条件的出栈,当前节点入栈。
- 栈顶元素<=a[i],节点入栈。
情况1,则对于弹出的所有元素,它们向右的比他小的节点都是a[i],这个容易理解。
情况2,栈内有元素,那么栈顶元素必定<=a[i],我们将当前元素的左边第一个比它小的元素定义为栈顶元素。这样的话,如果两个小矩阵高相同,右边的矩阵会出现错误,左边的矩阵面积计算正常,所以总的来说不会有事。
也可以采用一个单调非减栈和一个单调非增栈,在注释里我也写了单调非增栈。
总结:
单调栈的利用在本题中可谓是经典。通过出栈进栈可以很好的计算每个节点的左右小节点。值得注意的是,在刚开始我没有将a[0] a[n+1]设置为最小值,搞了很多特判,最后还错了,这样的小技巧可要掌握好。
代码:
#include <iostream>
#include <string.h>
#include <algorithm>
const int N = 100000 + 100;
using namespace std;
long long n, a[N], L[N], R[N], st[N];
void solve1()
{
int l = 1, r = 0; // r-l+1 = 0
a[0] = a[n + 1] = INT_MIN;
for (int i = 0; i <= n + 1; i++)
{
while (l <= r && a[st[r]] > a[i])
{
R[st[r]] = i - 1;
r--; //弹栈
}
st[++r] = i; //入栈
if (l <= r) //栈不为空,说明此时栈里面里面的下一个元素就是<=它的元素(永远不可能空,因为第一个元素设置为最小值)
L[st[r]] = st[r - 1] + 1; //比栈顶元素小(或等于)的是里面的一个元素
}
}
/*
void solve2()
{
int l = n + 1, r = n; // r-l+1 = 0
for (int i = n; i >= 1; i--)
{
while (l <= r && a[st[l]] > a[i])
{
L[st[l]] = i;
l++;
}
st[--l] = i;
}
}
*/
int main()
{
while (scanf("%d", &n) && n != 0)
{
memset(a, 0, sizeof(a));
memset(L, 0, sizeof(L));
memset(R, 0, sizeof(R));
memset(st, 0, sizeof(st));
for (int i = 1; i <= n; i++)
cin >> a[i];
solve1();
long long maxx = INT_MIN;
for (int i = 1; i <= n; i++)
{
if (maxx < (R[i] - L[i] + 1) * a[i])
maxx = (R[i] - L[i] + 1) * a[i];
}
cout << maxx << endl;
}
//system("pause");
return 0;
}
B - TT’s Magic Cat
题意:
给定数组a,给定操作q个操作:l,r,c,代表将a数组从a[l到a[r]全部加上c,问q个操作后a数组的结果
思路:
这是一道再普通不过的差分题。
当处理一个数组,需要将其区间+或-某个数的时候,就可以用到差分。
将对一个数组的操作,转换成对两个数的操作,最后求前缀和可以重新输出数组。
总结:
差分的思想还是很重要的,虽然不难,但是要好好掌握。对区间[l,r]进行操作的时候,不要忘记是对应的b[l]和b[r+1]进行操作,别错了。
代码:
#include <iostream>
using namespace std;
const int N = 200000 + 10;
int n, q, a[N], b[N];
int main()
{
cin >> n >> q;
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = 1; i <= n; i++) //求差分
b[i] = a[i] - a[i - 1];
for (int i = 1; i <= q; i++)
{
int l, r, c;
cin >> l >> r >> c;
b[l] += c;
b[r + 1] -= c;
}
long long ans = 0;
for (int i = 1; i <= n; i++) //前缀和将差分转换回去
{
ans += b[i];
cout << ans << " ";
}
cout << endl;
//system("pause");
return 0;
}
C - 平衡字符串
题意:
一个长度为 n 的字符串 s,其中仅包含 ‘Q’, ‘W’, ‘E’, ‘R’ 四种字符。如果四种字符在字符串中出现次数均为 n/4,则其为一个平衡字符串。
现可以将 s 中连续的一段子串替换成相同长度的只包含那四个字符的任意字符串,使其变为一个平衡字符串,问替换子串的最小长度?
如果 s 已经平衡则输出0
思路:
这题因为需要改变一段序列,并且需要从左向右的搜索,所以比较适合尺取法。
尺取法的思路比较简单,从最左边开始一个最小区间,如果该区间符合要求就移动l,不符合要求就移动r,直到r移出整个数组。值得一提的是,当l==r时,需要l和r都+1.
对于本题来说,我们需要考虑的是用一种简单方法判断区间变换后是否符合要求。因为我们要求四种字符数量相同,所以我们需要判断区间内能否满足这个要求。所以主要思路有两点
- 用sumq, sumw, sume, sumr来记录不包含区间[l,r]时,字符’Q’, ‘W’,‘E’,'R’的个数。
- 先通过替换,使得4类字符的数量一致,然后判断剩余的空闲位置是否是4的倍数。
总结:
如何判断区间符合要求是个问题。学长上课讲了思路所以很简单。。。但是如果学长不讲我觉得我得想一阵子。尺取法的模板要熟练掌握。
代码:
#include <iostream>
using namespace std;
string s;
int ans = INT_MAX;
int l, r;
int sum[4];
bool check() //判断是否满足要求
{
int maxx = max(sum[0], max(sum[1], max(sum[2], sum[3])));
int total = r - l + 1;
int free = total - ((maxx - sum[0]) + (maxx - sum[1]) + (maxx - sum[2]) + (maxx - sum[3]));
if (free % 4 == 0 && free >= 0)
{
ans = min(ans, total);
return true;
}
return false;
}
void Minus(int x)
{
if (s[x] == 'Q')
sum[0]--;
else if (s[x] == 'W')
sum[1]--;
else if (s[x] == 'E')
sum[2]--;
else if (s[x] == 'R')
sum[3]--;
}
void Plus(int x)
{
if (s[x] == 'Q')
sum[0]++;
else if (s[x] == 'W')
sum[1]++;
else if (s[x] == 'E')
sum[2]++;
else if (s[x] == 'R')
sum[3]++;
}
int main()
{
cin >> s;
for (int i = 0; i < s.size(); i++)
Plus(i);
//如果不预先处理会很麻烦
if (sum[0] == s.size() / 4 && sum[1] == s.size() / 4 && sum[2] == s.size() / 4 && sum[3] == s.size() / 4)
{
cout << 0 << endl;
return 0;
}
Minus(0);
while (r < s.size())
{
if (check())
{
Plus(l);
if (l == r)
Minus(++r);
l++;
}
else
Minus(++r);
}
cout << ans << endl;
//system("pause");
return 0;
}
D - 滑动窗口滑动窗口
题意:
ZJM 有一个长度为 n 的数列和一个大小为 k 的窗口, 窗口可以在数列上来回移动. 现在 ZJM 想知道在窗口从左往右滑的时候,每次窗口内数的最大值和最小值分别是多少.
思路:
这道题要查找一个窗口的最大值和最小值,是一种局部,单调栈并不适合局部问题,单调队列可以维护局部的单调性,因此这类题均可以使用单调队列来求解。当超出窗口限制时弹出队首,当不满足单调性时弹出队尾。
而针对本题来说,因为需要求最小值和最大值,所以需要维护两个单调队列。
总结:
单调队列的代码和单调栈非常相似,理解之后写起来就不难了。
因为这道题TE了好几次,所以又是inline,又是去缓冲解绑定。结果发现是一个地方写错了。还是要细心啊。
代码:
#include <iostream>
using namespace std;
const int N = 1e6 + 10;
int n, k, a[N], que[N], num2[N], num1[N];
//模仿单调栈,代码较为类似
inline void solve1()
{
int l = 1, r = 0;
for (int i = 1; i <= n; i++)
{
while (r >= l && a[que[r]] >= a[i])
r--;
que[++r] = i;
if (que[r] - que[l] + 1 > k)
l++;
num1[i] = a[que[l]];//最小值
}
}
inline void solve2()
{
int l = 1, r = 0;
for (int i = 1; i <= n; i++)
{
while (r >= l && a[que[r]] <= a[i])
r--;
que[++r] = i;
if (que[r] - que[l] + 1 > k)
l++;
num2[i] = a[que[l]];//最大值
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> k;
for (int i = 1; i <= n; i++)
cin >> a[i];
solve1();//单调递增队列
solve2();//单调递减队列
for (int i = k; i <= n; i++)
cout << num1[i] << " ";
cout << endl;
for (int i = k; i <= n; i++)
cout << num2[i] << " ";
cout << endl;
//system("pause");
}
总结:
这周学了单调栈,单调队列,差分法,尺取法等方法。
单调栈和单调队列有异曲同工之处,需要好好理解,才能写出代码。
差分法和尺取法相对来说较为简单,需要熟练写出代码。
推荐阅读
-
《C语言及程序设计初步》_1.11算术运算符与算术表达式_实践9——分离各位数
-
《C语言及程序设计初步》_1.11算术运算符与算术表达式_实践10——分离整数和小数部分
-
程序设计思维与实践 Week5 作业 B - TT's Magic Cat
-
程序设计基石与实践系列之类型提升、内存分配,数组转指针、打桩和矢量变换
-
《C语言及程序设计初步》_1.11算术运算符与算术表达式_实践11——如何买玫瑰
-
《C语言及程序设计初步》_1.11算术运算符与算术表达式_实践12——玩数字
-
《C语言及程序设计》实践项目11 算术运算符与算术表达式
-
程序设计思维与实践 CSP-M2 (3/4/数据班)
-
人机智能交互技术(ROS)实践作业模版与说明
-
《C语言及程序设计初步》_1.11算术运算符与算术表达式_实践10——分离整数和小数部分