题目描述
有 \(n\) 座灯塔排成一排,第 \(i\) 座灯塔的高度是 \(a_i\)。你需要求出有多少对 \(l < r\) 满足 \(a_l = a_r\),且 \(\forall l < i < r, a_i < a_l\)。
灯塔的高度有 \(Q\) 次修改,每次给定 \(x, h\),表示将 \(a_x\) 修改为 \(h\)。求出修改之前和每次修改之后的答案。
\(n\leq 10^5, Q\leq 2.5\times 10^5,1\leq a_i, h\leq 10^9\)。部分分:保证只有至多 \(1000\) 座灯塔的高度发生过变化。
solution n <= 1000
单调栈随便做。。。
solution 部分分
将修改过的灯塔称为动点,没改过的灯塔称为静点。动点很少,考虑动点与动点之间、动点与静点之间的直接暴力。题解说可以 \(O(1000Q)\),可能的做法是对于动点-动点区间每次都跑单调栈,动点-静点区间可以忽略掉所有动点的影响预先跑单调栈(每个动点最多两个可能的动点-静点区间),然后维护有多少个动点正在刺破这个动点-静点区间,修改之后 \(O(Q)\) 地维护刚才说的东西。
至于静点-静点区间,这个才是大头。首先无视所有动点,单调栈跑出所有 \(O(n)\) 个区间。观察到这些区间要么不交要么包含(可能共端点,考虑连续三个相同的数),那么惯用手法就是建一棵树出来,互相包含的区间为祖孙关系,相当于整了一种很新的笛卡尔树出来,也用单调栈建。注意要使树上的每个点都代表一个区间。然后考虑动点影响,动点可能会刺穿树上的一条直链(深度递增的链),且这条链要么是空要么链底总是固定的,预处理这个链底(不一定是叶子哦),每次修改之后在树上倍增或者树剖等在树上跳,在刺穿的直链上打上标记(不覆盖是因为标记需要撤销),这部分依据实现写树剖线段树是 \(O(Q\log^2 n)\)。
solution 正解
可以将部分分做法和根号重构结合一下,出题人说过不了。所以考虑线段树分治,在向下递归的过程中,越来越多的动点被固定下来转化为静点,此时要考虑这些动点对祖先的静点的影响。在每个节点都建好静点-静点的区间的树,在子树中的动点上来刺树时,用一些单调栈、双指针技巧将这些动点在树上的位置找出来,然后树剖线段树。
但是你仔细算一下这么搞的复杂度。首先算动点定位,每个点上挂的节点都要进行子树大小加深度次扫描,只要根节点上挂足够多全局静点就会 TLE。还有树剖部分,一共 \(O((n+Q)\log Q)\) 个点,每个点需要去找 \(\log Q\) 个祖先的树,各自在 \(\log Q\) 棵树上进行 \(O(\log^2n)\) 的树剖,总计 \(O((n+Q)\log^2Q\log^2n)\),直接升天。
解决方法是换个方向,考虑这个节点的树对子树内的点的贡献,因为我们知道长度为 \(l\) 的区间内至多有 \(O(l)\) 个本质不同点(即这段时间内被修改过的点,否则它不会进入这个子树),可以找出这些点,然后在树上定位它们(复杂度 \(O((n+Q)\log Q)\),算上排序再乘一个 \(O(\log n)\)),然后可以暴力在子树内 dfs 做线段树分治的过程(共 \(O(Q\log Q)\) 次调用树剖线段树)或者常数小一点的话先将涉及到的点在这段时间之前的状态处理出来,计算它们在树上的贡献,然后再扫描这段时间的所有修改。
至此本题可以在大约 \(O((n+Q)\log^2Q+Q\log Q\log^2n)\) 的时间内解决(使用树剖线段树),写全局平衡二叉树可以优化到 \(O(n\log^2n)\)(视 \(n, Q\) 同阶)。
实现细节
线段树二分
写 zkw 线段树,我们需要单点修改,找 \(x\) 前面第一个不小于 \(a_x\) 的数,\(x\) 后面第一个不小于 \(a_x\) 的数,可以看一下怎么写,这个是真的妙。
全局平衡二叉树
【模板】全局平衡二叉树 - caijianhong - 博客园 (cnblogs.com)
可参考下面的实现,感觉写的挺好。注意这里有一个线段树二分(平衡树二分?),只会在最后一次调用时发生递归,且因为二叉树深度不超过 \(O(\log n)\) 所以单次操作还是 \(O(\log n)\)。
单调栈
对这些区间的某一个端点排序,稍微推导一下就能写,注意别写假了。
code
#include <bits/stdc++.h>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr, ##__VA_ARGS__)
#else
#define endl "\n"
#define debug(...) void(0)
#endif
using LL = long long;
vector<pair<int, int>> Q[1000010];
void insqry(int L, int R, int i, int h, int p, int l, int r) {/*{{{*/
if (L <= l && r <= R) return Q[p].emplace_back(i, h), void();
int mid = (l + r) >> 1;
if (L <= mid) insqry(L, R, i, h, p << 1, l, mid);
if (mid < R) insqry(L, R, i, h, p << 1 | 1, mid + 1, r);
}/*}}}*/
template <int N>
struct zkwtree {/*{{{*/ // 线段树二分
int mx[N << 1];
static_assert((N & (N - 1)) == 0);
void mdf(int x, int v) { for (mx[x += N] = v; x >>= 1; mx[x] = max(mx[x << 1], mx[x << 1 | 1])); }
int fdpre(int x) {
int v = mx[x += N];
for (; x; x >>= 1) if (x & 1 && mx[x ^ 1] >= v) { x ^= 1; break; }
while (x && x < N) x = x << 1 | (mx[x << 1 | 1] >= v);
return x && mx[x] == v ? x - N : 0;
}
int fdnxt(int x) {
int v = mx[x += N];
for (; x; x >>= 1) if (~x & 1 && mx[x ^ 1] >= v) { x ^= 1; break; }
while (x && x < N) x = x << 1 | !(mx[x << 1] >= v);
return x && mx[x] == v ? x - N : 0;
}
};/*}}}*/
zkwtree<1 << 17> zkw;
constexpr int N = 1e5 + 10;
pair<int, int> opt[250010], H[200010];
int n, q, cnt, ans[250010], a[100010], lst[100010], stk[N], b[100010];
namespace seg { // 全局平衡二叉树(懒标记是扫描线写法)
int tag[N], len[N], ch[N][2], tf[N], vl[N], vr[N], his[N], val[N];
int fa[N], dfn[N], rnk[N], siz[N], son[N], dep[N], top[N];
void maintain(int p) {
if (tag[p]) len[p] = 0;
else len[p] = len[ch[p][0]] + !his[p] + len[ch[p][1]];
}
int build(int l, int r) {
if (l > r) return 0;
int L = l, R = r, ans = l, T = siz[rnk[l]] - siz[son[rnk[r]]];
while (L <= R) {
int mid = (L + R) >> 1;
if ((siz[rnk[l]] - siz[son[rnk[mid]]]) * 2 <= T) ans = mid, L = mid + 1;
else R = mid - 1;
}
int p = rnk[ans];
if ((ch[p][0] = build(l, ans - 1))) tf[ch[p][0]] = p;
if ((ch[p][1] = build(ans + 1, r))) tf[ch[p][1]] = p;
tag[p] = his[p] = 0;
maintain(p);
vl[p] = val[rnk[l]], vr[p] = val[rnk[r]];
return p;
}
void binary(int p, int v, int k) {
if (vl[p] <= v) return tag[p] += k, maintain(p);
if (vr[p] > v) return ;
if (ch[p][0]) binary(ch[p][0], v, k);
if (val[p] <= v) his[p] += k;
if (ch[p][1]) binary(ch[p][1], v, k);
maintain(p);
}
int update(int p, int v, int k) {
int del = 0, lst = -1;
while (p) {
if (ch[tf[p]][0] != p && ch[tf[p]][1] != p) del -= len[p];
if (ch[p][0] != lst) {
if (val[p] <= v) his[p] += k;
if (ch[p][0]) binary(ch[p][0], v, k);
}
maintain(p);
if (ch[tf[p]][0] != p && ch[tf[p]][1] != p) {
del += len[p];
if (vl[p] >= v) break;
}
lst = exchange(p, tf[p]);
}
return del;
}
vector<int> G[N];
void dfs(int u, int f) {
fa[u] = f, siz[u] = 1, dep[u] = dep[f] + 1, son[u] = 0;
for (int v : G[u]) if (v != f) dfs(v, u), siz[u] += siz[v], siz[son[u]] < siz[v] && (son[u] = v);
}
void cut(int u, int topf) {
dfn[u] = ++cnt, rnk[cnt] = u, top[u] = topf;
if (son[u]) cut(son[u], topf);
else seg::tf[seg::build(dfn[topf], dfn[u])] = fa[topf];
for (int v : G[u]) if (v != fa[u] && v != son[u]) cut(v, v);
}
}; // namespace seg
template <class T> void unique_proc(T arr[], int &len) {
sort(arr + 1, arr + len + 1);
len = (int)(unique(arr + 1, arr + len + 1) - arr - 1);
}
void build(const vector<pair<int, int>>& Qs) { // 当前区间的点与祖先的点组成的区间,建树
int top = cnt = 0;
for (auto e : Qs) {
int i = e.first;
if (int lpos = zkw.fdpre(i)) H[++cnt] = make_pair(lpos, i);
if (int rpos = zkw.fdnxt(i)) H[++cnt] = make_pair(i, rpos);
}
unique_proc(H, cnt);
for (int i = 1; i <= cnt; i++) {
seg::G[i].clear(), seg::dfn[i] = 0, seg::val[i] = a[H[i].first];
while (top && H[stk[top]].second <= H[i].first) --top;
if (top && H[stk[top]].second >= H[i].second) seg::G[stk[top]].push_back(i);
stk[++top] = i;
}
int cnt0 = exchange(cnt, 0);
for (int i = 1; i <= cnt0; i++) if (!seg::dfn[i]) seg::dfs(i, 0), seg::cut(i, i);
}
int ont[100010], vec[350010];
void solve(int p, int l, int r) {
for (auto e : Q[p]) zkw.mdf(e.first, a[e.first] = e.second);
build(Q[p]);
if (l == r) ans[l] += cnt;
else if (cnt) {
int tot = 0;
for (int pd = p << 1, rd = (l + r) >> 1; l <= rd; pd <<= 1, rd = (l + rd) >> 1) {
for (auto e : Q[pd]) vec[++tot] = e.first;
if (l == rd) break;
} // 所被修改的点
ont[opt[l].first] = 0; // ont[x] 表示 x 的定位结果,注意这段时间的第一个修改可能不能对树产生影响。
//for (int i = l + 1; i <= r; i++) assert(!a[opt[i].first]), vec[++tot] = opt[i].first;
unique_proc(vec, tot);
auto mapping = [&]() {/*{{{*/ // 动点定位
int top = 0, itl = cnt, itr = tot;
for (int k = 1; k <= cnt + tot; k++) {
if (itl && (!itr || H[itl].first > vec[itr])) {
while (top && stk[top] <= H[itl].second) ont[stk[top--]] = itl;
--itl;
} else stk[++top] = vec[itr--];
}
while (top) ont[stk[top--]] = 0;
};/*}}}*/
mapping();
int now = cnt;
for (int i = 1; i <= tot; i++) { // 处理为这段时间前的状态
int x = vec[i];
now += seg::update(ont[x], a[x] = b[x], +1);
}
if (!l) ans[l] += now;
for (int i = l + !l; i <= r; i++) {
int x = opt[i].first;
now += seg::update(ont[x], a[x], -1); // update 返回增量,维护增量
now += seg::update(ont[x], a[x] = opt[i].second, +1);
ans[i] += now;
}
for (int i = 1; i <= tot; i++) a[vec[i]] = 0;
}
if (l != r) {
int mid = (l + r) >> 1;
solve(p << 1, l, mid);
solve(p << 1 | 1, mid + 1, r);
} else if (l) {
b[opt[l].first] = opt[l].second;
}
for (auto e : Q[p]) zkw.mdf(e.first, a[e.first] = 0);
}
int main() {
#ifndef LOCAL
cin.tie(nullptr)->sync_with_stdio(false);
#endif
cin >> n >> q;
for (int i = 1; i <= n; i++) cin >> a[i];
memcpy(b, a, sizeof b);
for (int t = 1, i; t <= q; t++) {
cin >> i;
opt[t].first = i;
insqry(lst[i], t - 1, i, a[i], 1, 0, q);
lst[i] = t;
cin >> a[i];
opt[t].second = a[i];
}
for (int i = 1; i <= n; i++) insqry(lst[i], q, i, a[i], 1, 0, q);
memset(a, 0, sizeof a);
solve(1, 0, q);
for (int i = 0; i <= q; i++) cout << ans[i] << endl;
return 0;
}
标签:siz,动点,log,int,题解,len,静点,GD230531C,眺望
From: https://www.cnblogs.com/caijianhong/p/18423275