单调栈:Codeforces Round 622 (Div. 2) C2. Skyscrapers (hard version)
简单来讲就是最后需要呈现出一个单峰数组,使得总高度最高。
最开始想到暴力枚举每一个元素都充当最高的“单峰”,但是这里的 n 过大,这样枚举肯定会TLE。
那就考虑能不能单调线性的考虑每个元素作为最高点的时候的解是多少呢?
这里就需要借助我们的 单调栈,维护一个单调递增的序列:
- 这里仅以正序遍历为例:f[i]表示的是以i为单峰时1 — i 所有数组能产生的最大贡献,根据单调栈的性质,stk.top()就是上一个最近的小于 a[i] 的元素的下标,所以加上这中间所有的楼产生的贡献(由于单调栈的性质,这段中所有大于a[i] 的元素一定会被弹出,同时减去他们之前产生的贡献)。
#include<bits/stdc++.h>
using namespace std;
#define int long long
signed main() {
int n, sum = 0;
cin >> n;
vector<int> a(n + 1);
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
stack<int> stk;
vector<int> f(n + 1, 0);
sum = 0;
for (int i = 1; i <= n; i++) {
while(stk.size() && a[stk.top()] >= a[i]) {
//先弹出所有大于a[i]的楼的下标,因为保证要单增
int j = stk.top();
stk.pop();
sum -= (j - (stk.empty() ? 0 : stk.top())) * a[j];
// 减去这些楼之前所产生的贡献
}
sum += (i - (stk.empty() ? 0 : stk.top())) * a[i]; //加上目前这栋楼所产生的贡献
stk.push(i);
f[i] += sum;
}
sum = 0;
while(!stk.empty()) stk.pop();
for (int i = n; i >= 1; i--) {
while(stk.size() && a[stk.top()] >= a[i]) {
int j = stk.top();
stk.pop();
sum -= ((stk.empty() ? n + 1 : stk.top()) - j) * a[j];
}
sum += ((stk.empty() ? n + 1 : stk.top()) - i) * a[i];
stk.push(i);
f[i] += sum - a[i];
}
auto p = max_element(f.begin() + 1, f.end()) - f.begin();
//cout << p << endl;
for (int i = p - 1; i >= 1; i--) {
a[i] = min(a[i], a[i + 1]);
}
for (int i = p + 1; i <= n; i++) {
a[i] = min(a[i], a[i - 1]);
}
for (int i = 1; i <= n; i++) {
cout << a[i] << " \n"[i == n];
}
return 0;
}
建议先熟练掌握单调栈再来理解这题。
树状数组:Codeforces Round 1013 (Div. 3) F. Igor and Mountain
其实也不一定需要树状数组,用前缀和也能达到一样的效果,只是树状数组比较好写,不费脑子。
简单来说就是如果暴力枚举转移的话会超时,所以就需要记录下整个区间的某种贡献,然后一起转移,这样就可以省下很多的时间。
i64 mod = 998244353;
double get_dist(int a, int b, int c, int d) {
double dx = fabs(a - c);
double dy = fabs(b - d);
return sqrt(dx * dx + dy * dy);
}
template <typename T>
class Fenwick {
public:
int n;
std::vector<T> a;
Fenwick(int n_ = 0) {
init(n_);
}
void init(int n_) {
n = n_;
a.assign(n + 1, T{});
}
void add(int x, const T &v) {
for (int i = x; i <= n; i += i & -i) {
a[i] = (a[i] + v % mod + mod) % mod;
}
}
// 查询位置 [1, x] 的前缀和
T sum(int x) {
T ans{};
for (int i = x; i > 0; i -= i & -i) {
ans = (ans + a[i] % mod + mod) % mod;
}
return ans;
}
// 查询区间 [l, r] 的区间和
T rangeSum(int l, int r) {
return (sum(r) - sum(l - 1) + mod) % mod;
}
int select(const T &k) {
int x = 0;
T cur{};
for (int i = 1 << std::__lg(n); i; i /= 2) {
if (x + i <= n && cur + a[x + i] <= k) {
x += i;
cur = cur + a[x];
}
}
return x;
}
};
void solve()
{
int n, m, d1, d2;
cin >> n >> m >> d1;
for (int i = 0; i <= d1; i++) {
if(i * i + 1 <= d1 * d1) {
d2 = i;
}
}
vector<string> g(n + 1);
for (int i = 1; i <= n; i++) {
cin >> g[i];
g[i] = ' ' + g[i];
}
int ans = 0;
vector<Fenwick<int>> f(n + 1, Fenwick<int>(m + 1)), nf(f);
for (int j = 1; j <= m; j++) {
if (g[n][j] == 'X') {
f[n].add(j, 1);
}
}
for (int j = 1; j <= m; j++) {
if (g[n][j] == 'X') {
nf[n].add(j, f[n].rangeSum(max(1LL, j - d1), min(j + d1, m)));
}
}
// for (int j = 1; j <= m; j++) {
// cout << nf[n].rangeSum(j, j) << ' ';
// }
// cout << endl;
for (int i = n - 1; i >= 1; i--) {
for (int j = 1; j <= m; j++) {
if (g[i][j] == 'X') {
f[i].add(j, nf[i + 1].rangeSum(max(1LL, j - d2), min(j + d2, m)));
}
}
for (int j = 1; j <= m; j++) {
if (g[i][j] == 'X') {
nf[i].add(j, f[i].rangeSum(max(1LL, j - d1), min(j + d1, m)));
}
}
}
ans = nf[1].sum(m);
cout << (ans + mod) % mod << endl;
}