2022 03 06 栈&滑动窗口【未完成】
单调栈 | CEOI 2011 – Balloons | 提高+ | https://www.luogu.com.cn/problem/P4697 |
---|---|---|---|
单调栈 | CEOI 2020 - Fancy Fence | NOI- | https://www.luogu.com.cn/problem/P6801 |
括号序列 | USACO 2017 Open S - Modern Art 2 | 提高+ | https://www.luogu.com.cn/problem/P3668 |
单调栈 | CF gym101102D Rectangles, ACPC2016 | 提高+ | https://codeforces.com/gym/101102/problem/D |
单调栈 | IOI 2004 - Empodia | NOI- | https://www.luogu.com.cn/problem/P5923 |
滑动窗口 | USACO 2016 Jan P. Fort Moo | 提高+ | https://www.luogu.com.cn/problem/P3135 |
---|---|---|---|
扫描线 | APIO 2015 - Palembang Bridges | NOI- | https://www.luogu.com.cn/problem/P3644 |
扫描线 | USACO 2019 Feb G. Painting the Barn | NOI- | https://www.luogu.com.cn/problem/P6100 |
扫描线 | APIO 2009 - Digging for Oil | NOI- | https://www.luogu.com.cn/problem/P3625 |
扫描线 | IOI 2005 - Garden | 提高+ | https://www.spoj.com/problems/IOIGARD/en/ |
Balloons¶
有两个关键点:对于 \(i < j < k\),\(x_i < x_j < x_k\),那么
- 对于 \(r_i \le r_j\),气球 \(i\) 不会对 \(j\) 以后的气球产生影响
- 对于 \(r_i > r_j\),若 \(r_k < r_j\),那么 \(i\) 不会对 \(k\) 产生影响,\(j\) 可能对 \(k\) 产生影响
所以可维护一个单调递减的栈,
- 取出栈顶 \(j\) 对当前决策点 \(k\) 进行决策;
- 若 \(r_k < r_j\),则它不会受前面的气球的影响了。把 \(r_k\) 加入栈顶,退出;
- 否则弹出栈顶 \(j\)(为了维护 \(r_j\) 递增),回到 1。
时间复杂度:\(O(n)\)
/**
* @file luogu/4697/main.cpp
* @brief
* @see
* @author Ruiming Guo (guoruiming@stu.scu.edu.cn)
* @copyright 2022
* @date 2022/6/23 13:30:37
**/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef vector<int> vi;
const int N = 200005;
int n, stk[N], top;
double r[N], x[N];
int main() {
// High rating and good luck!
scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
scanf("%lf%lf", &x[i], &r[i]);
while (top) {
int j = stk[top];
double limit = (x[i] - x[j]) * (x[i] - x[j]) / (4.0 * r[j]);
if (r[i] > limit) r[i] = limit;
if (r[i] <= r[j])
break;
else
--top;
}
stk[++top] = i;
printf("%.6lf\n", r[i]);
}
return 0;
}
花式围栏¶
数这里有多少个矩形。
考虑维护一个高度递增的单调栈。
不妨设
- 当前要加入的矩形为 \(a\) ,高度为 \(h\)
- 单调栈顶部的矩形为 \(x\),高度为 \(h_1\),宽为 \(w_1\)
- 单调栈次栈顶矩形为 \(y\),高度维 \(h_2\)
如果 \(h<h_1\),\(x\) 高于 \(a\) 的部分不会对后加入的矩形产生影响,将 \(x\) 的高度削到 \(\max(h, h_2)\)
接着考虑削去该矩形对答案的贡献,即求出“四边都落在 \(x\) 内部(包括边界)且至少有一边落在被削去矩形
考虑用单调栈维护高度单调递增的矩形。
现在假如我们需要将第 \(i\) 个矩形插入单调栈中,如果该矩形比栈顶矩形高度更高则直接插入,否则我们需要将高出当前矩形的部分删除,并累加该部分的答案。
假如当前栈顶的矩形宽为 \(w_p\),高为 \(h_p\),栈顶下面的矩形宽为 \(w_n\),高为 \(h_n\),要插入的矩形宽为 \(w_i\),高为 \(h_i\)。容易发现,我们需要将栈顶的矩形的高度降至 \(\max(h_n,h_i)\)
才能不破坏单调性。
现在问题变成了如何计算被删除部分的贡献。
容易发现,这部分对答案的贡献为所有两个顶点落在被删除部分,其他两个顶点在被删除部分以外的矩形的个数。由容斥原理可知,答案即为栈顶矩形的子矩形个数,减去删除超高部分后的矩形的子矩形个数。
而根据组合数学知识可知,一个 \(w \times h\) 的矩形,其子矩形个数为 \(\dfrac{w \times (w+1)}{2} \times \dfrac{h \times (h+1)}{2}\)。
/**
* @file luogu/6801/main.cpp
* @brief
* @see https://www.luogu.com.cn/record/77840112
* @author Ruiming Guo (guoruiming@stu.scu.edu.cn)
* @copyright 2022
* @date 2022/6/23 21:25:48
**/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef vector<int> vi;
const int MOD = 1e9 + 7;
const int N = 100005;
ll h[N], w[N];
stack<int> s;
ll C2(ll x) { return x * (x - 1) / 2 % MOD; }
int main() {
// High rating and good luck!
int n;
ll ans = 0;
cin >> n;
for (int i = 1; i <= n; ++i) cin >> h[i];
for (int i = 1; i <= n; ++i) cin >> w[i];
s.push(0);
// 注意终止条件为 n+1,最后推进一个空矩形得到结果
for (int i = 1; i <= n + 1; ++i) {
// 考虑第 i 个矩形
ll sw = 0;
// 如果这个矩形比栈顶矩形低
while (h[s.top()] > h[i]) {
int u = s.top();
s.pop();
sw = (sw + w[u]) % MOD; // 当前超高部分的宽度
ans = (ans +
(C2(h[u] + 1) - C2(max(h[i], h[s.top()]) + 1)) // 公式的变形
* C2(sw + 1) % MOD +
MOD) %
MOD;
}
w[i] += sw; // 加宽矩形 i(保证栈的单调性)
s.push(i);
}
cout << ans << endl;
return 0;
}
Modern Art 2¶
给定 \(N\) 中颜色和一张布条,每种颜色只能使用一次;每天可以选取一些颜色对布条染色,每天的染色区间不能相交;后来的每天可以选另外一些颜色继续染色,可以覆盖前些天的染色。给定一条染色结束的布条,问用多少天可以如是复制一份,或者不能复制。
可以看出颜色必须是一层包一层,如 ABCCBA
;如果出现 ABAB
的情况则为不可能。
用栈判断:
- 如果下一个字符 \(a\) 和栈顶字符相同,不变;
- 与栈顶字符不同,一直出栈直到栈顶字符与 \(a\) 相同,同时标记出栈的字符;如果栈空了都不同那就是错
- \(a\) 是一个已经出栈的字符就是错
/**
* @file luogu/3668/main.cpp
* @brief
* @see https://www.luogu.com.cn/record/77842375
* @author Ruiming Guo (guoruiming@stu.scu.edu.cn)
* @copyright 2022
* @date 2022/6/24 20:32:32
**/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef vector<int> vi;
const int N = 1e5 + 10;
int n, a[N];
int s[N], t[N]; // s 为当前点是哪一个点的开头,t 表示当前点是那一个点的结尾
int x[N], y[N]; // 每种颜色的开始位置和结束位置
int top, ans;
int stk[N];
int main() {
// High rating and good luck!
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
y[a[i]] = i; // 颜色 a[i] 最后出现的位置
if (x[a[i]] == 0) x[a[i]] = i; // 颜色 a[i] 最先出现的位置
}
for (int i = 1; i <= n; ++i) {
if (x[i] != 0) s[x[i]] = i; // s 是颜色 i 的开头
if (y[i] != 0) t[y[i]] = i; // t 是颜色 i 的结尾
}
for (int i = 1; i <= n; ++i) {
if (a[i] == 0) { // 一笔刷下来,颜色中间不能有空白
if (top != 0) {
ans = -1;
break;
}
} else {
if (s[i] != 0) { // 当前点是一个点的开头
stk[++top] = s[i]; // 多了一种颜色
ans = max(ans, top);
}
if (stk[top] != a[i]) { // 出现了颜色交叉的情况
ans = -1;
break;
}
if (t[i] != 0) { // 当前点是一个点的结尾
top--; // 少了一种颜色
}
}
}
cout << ans << endl;
return 0;
}
Rectangles¶
问一个大的矩阵里面有多少个子矩阵,满足每个子矩阵的每一个元素相等。
//
//Created by just_sort 2016/9/30 15:32
//Copyright (c) 2016 just_sort.All Rights Reserved
//
#include <set>
#include <map>
#include <queue>
#include <stack>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 1002;
int S[maxn][maxn], TOP[maxn][maxn];
int A[maxn],B[maxn],sz,sum;
int n,m;
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
scanf("%d",&S[i][j]);
}
}
long long ans = 0;
for(int i = 1; i <= n; i++){
sz = sum = 0;
for(int j = 1; j <= m; j++){
int h = (S[i][j] == S[i-1][j] ? TOP[i-1][j] + 1 : 1);
int w = 1;
TOP[i][j] = h;
if(S[i][j] != S[i][j-1]) sum = sz = 0;
while(sz && h <= A[sz]){
sum -= A[sz] * B[sz];
w += B[sz--];
}
A[++sz] = h;B[sz] = w;
sum += A[sz] * B[sz];
ans += sum;
}
}
printf("%lld\n",ans);
}
return 0;
}
Empodia¶
这题可以看出数列是一个\(1\) 到 \(n\) 的全排列,可知满足 empodia 条件的的 \(i, j\) 必然满足
转换得到
枚举等式中的 \(i, j\) 即可,但时间复杂度 \(O(n^2)\),不可能。
考虑如下优化:
设 \(f[i]\) 为该点作为最大值能够向左边延伸到的最左位置,\(g[i]\) 为该点作为最小值能向右延伸到的最右位置,这是一个二维偏序。对于点对 \((i, j)\),\(f[j] \le i\) 且 \(g[j] \ge j\) 即为满足条件。
因为区间之间不存在包含关系,故可维护 \(g[i]\) 的单调栈,取尽可能小的 \(i\)。
Let \(v[0],v[1],\ldots,v[n-1]\) denote the input sequence. An interval \([i \ldots j]\) is framed if all three of the following conditions hold:
- \(v[i]−i=v[j]−j\)
- \(v[i]=\min(v[i…j])\)
- \(v[j]=\max(v[i\ldots j])\)
Our approach will be to iterate over all \(j=[0,n)\) and check whether \(j\) contributes a new empodio with right endpoint \(j\).
- Maintain a stack \(mn\) consisting of all indices \(i\) satisfying the second condition.
- When we increment \(j\), repeatedly pop the top element \(i\) of \(mn\) while \(v[i]>v[j]\). Then push \(j\) into \(mn\).
- To check whether \(j\) is part of an empodio, find the maximum \(i\in mn\) such that \(v[i]-i=v[j]-j\). If \(i\) is to the right of the rightmost left endpoint of any empodio found so far and \(v[j]=\max(v[i\ldots j])\), then we have found a new empodio.
To check whether \(v[j]=\max(v[i\ldots j])\), we can maintain a second stack \(mx\) that stores all indices \(i\) such that \(v[i]=\max(v[i\ldots j])\).
ref: https://usaco.guide/problems/ioi-2004empodia/solution
#include <algorithm>
#include <iostream>
using namespace std;
int n, r;
int A[1100002], C[1100002];
int main() {
ios_base::sync_with_stdio(0);
cin.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) cin >> A[i];
int i, j;
for (i = n; i; i--) {
int mayor = A[i];
for (j = i + 1; j <= n && A[j] > A[i]; j++) {
mayor = max(A[j], mayor);
if (A[j] == A[i] + j - i && A[j] == mayor) {
r++;
C[i] = j;
break;
}
if (C[j]) break;
}
}
cout << r << "\n";
for (i = 1; i <= n; i++)
if (C[i]) cout << i << " " << C[i] << "\n";
return 0;
}