数据结构
数据结构
训练成果





挂的都是 luogu 的题。
deepseek 竟然做对了一道题,恐怖如斯。
``
P3767 魔法
首先这个题的操作带有历史版本,每个版本有一个前驱,很容易将其转化为一个树形结构。
先不考虑删除操作,可以发现每个限制影响一个子树,在 \(dfn\) 序列上是一个区间。
再加上删除操作,删除操作也是影响一个区间,同一个操作删除它产生的区间肯定不交(题目保证),这样将删除区间剔除后剩下的总共是 \(\mathcal{O}(n)\) 个加入操作区间。
也就是说,这个问题变成了,每个限制一个区间,求当前限制下有没有合法方案。
这很像线段树分治啊。
限制是相生相克关系,这简直就是并查集可以维护的。
那么就是线段树分治维护可撤销并查集了,和那道经典的线段树分治模板很像。
如何用并查集呢?
将五行(顺时针)选不选分别定为 \(x, x + n, x + 2n, x + 3n, x + 4n\)。
那么由于 \(A\) 一定要生或克 \(B\),在五行图上不难发现生是模意义下 +1,克是模意义下 +2。
所以限制就变成了模意义下 \(A + kn\) 和 \(B + (k + t)n\) 取值相同。
那么将二者连边变成了并查集上的一颗树中元素值相同。
结论:如果不存在 \(k_1 \neq k_2\) 使得 \(x + k_1n\) 和 \(x + k_2n\) 出于同一颗并查集树中,那么一定存在合法解。
感性理解还是很简单的。
证一下:如果存在上述情况,那么由于 \(x + kn\) 对称,所以每个 \(x + kn\) 都会连向另一个 \(x + kn\),总不能同时选两个吧。
不存在上述情况时拎出来一个 \(x\) 则,其所在并查集会和其余四个并查集全等,那么取其中一个全部赋值为 \(1\) 即可构造出合法方案。
细节倒没有什么,会写线段树分治模板就行。
code
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 5e5 + 10;
#define emp emplace_back
int n, m, k;
struct Query
{
int x, y, op;
}Q[N];
class Union
{
public :
int f[N], timer, siz[N], w;
int stack[N], stack_f[N], stack_siz[N], stack_timer[N], stack_w[N], sizs;
void Save()
{
++timer;
}
int Find(int x)
{
return f[x] == x ? x : Find(f[x]);
}
void Add(int fx)
{
stack[++sizs] = fx;
stack_w[sizs] = w;
stack_siz[sizs] = siz[fx];
stack_timer[sizs] = timer;
stack_f[sizs] = f[fx];
}
void Merge(int x, int y)
{
int fx = Find(x), fy = Find(y);
if (fx == fy) return ;
if (siz[fx] > siz[fy]) swap(fx, fy);
Add(fx), Add(fy);
f[fx] = fy;
siz[fy] += siz[fx];
}
void Check(int x, int n)
{
int stac[5];
for (int i = 0; i < 5; i++) stac[i] = Find(x + i * n);
sort(stac, stac + 5);
for (int i = 0; i < 4; i++) if (stac[i] == stac[i + 1]) ++w;
}
void Undo()
{
--timer;
while (sizs && stack_timer[sizs] > timer)
{
int fx = stack[sizs];
f[fx] = stack_f[sizs];
siz[fx] = stack_siz[sizs];
w = stack_w[sizs];
--sizs;
}
}
}U;
class SemTree
{
public :
#define lid id << 1
#define rid id << 1 | 1
vector <int> q[N << 2];
int ans[N];
void Insert(int id, int cl, int cr, int l, int r, int k)
{
if (l <= cl && cr <= r) return q[id].emp(k), void();
int mid = (cl + cr) >> 1;
if (l <= mid) Insert(lid, cl, mid, l, r, k);
if (r > mid) Insert(rid, mid + 1, cr, l, r, k);
}
bool SafeInsert(int id, int cl, int cr, int l, int r, int k)
{
if (l > r)
{
// cerr << "Insert l > r, Wrong!\n";
return false;
}
// cerr << k << ' ' << l << ' ' << r << '\n';
Insert(id, cl, cr, l, r, k);
return true;
}
void Query(int id, int l, int r)
{
U.Save();
for (auto d : q[id])
{
if (Q[d].op == 1)
{
int x = Q[d].x, y = Q[d].y;
for (int i = 0; i < 5; i++) U.Merge(x + i * n, y + (i + 1) % 5 * n);
U.Check(x, n), U.Check(y, n);
}
if (Q[d].op == 2)
{
int x = Q[d].x, y = Q[d].y;
for (int i = 0; i < 5; i++) U.Merge(x + i * n, y + (i + 2) % 5 * n);
U.Check(x, n), U.Check(y, n);
}
}
if (l == r) return ans[l] = U.w, U.Undo(), void();
int mid = (l + r) >> 1;
Query(lid, l, mid), Query(rid, mid + 1, r);
U.Undo();
}
}T;
vector <int> G[N];
vector <pair <int, int>> deletePos[N];
int dfn[N], timer, out[N];
void dfs(int x)
{
if (x) out[x] = dfn[x] = ++timer;
for (auto to : G[x])
{
dfs(to);
out[x] = max(out[x], out[to]);
}
if (Q[x].op == 3) deletePos[Q[x].x].emp(dfn[x], out[x]);
}
signed main()
{
// freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= 5 * n; i++) U.siz[i] = 1, U.f[i] = i;
for (int i = 1; i <= m; i++)
{
int k, op; cin >> k >> op;
G[k].emp(i);
int u, v = 0; cin >> u;
if (op != 3) cin >> v;
Q[i] = {u, v, op};
}
dfs(0);
for (int i = 1; i <= m; i++)
{
if (Q[i].op == 3) continue;
if (!deletePos[i].size())
{
T.SafeInsert(1, 1, m, dfn[i], out[i], i);
continue;
}
int st = deletePos[i][0].second;
T.SafeInsert(1, 1, m, dfn[i], deletePos[i][0].first - 1, i);
for (auto nowPos : deletePos[i])
{
T.SafeInsert(1, 1, m, st + 1, nowPos.first - 1, i);
st = nowPos.second;
}
T.SafeInsert(1, 1, m, st + 1, out[i], i);
}
T.Query(1, 1, m);
for (int i = 1; i <= m; i++)
{
if (T.ans[dfn[i]]) cout << "naive\n";
else cout << "excited\n";
}
return 0;
}
P4254 [JSOI2008] Blue Mary 开公司
模板题直接略过。
P5607 [Ynoi2013] 无力回天 NOI2017
这个题有说法的。
首先感觉像是 bitset 题,可是有点太大了。
那或许可以根号分治,因为这是乘积有上界的。
但是传统的直接对集合大小根号分治对复杂度没有帮助,而所求是并,可以转化为求交集,那么就是所有同时存在与两个集合中的元素,可以对元素出现次数根号分治。
如果一个元素出现超过 \(B\) 次,那么这样的元素不超过 \(\frac{n}{B}\) 个,这部分可以用 bitset 维护,复杂度是 \(\mathcal{O}(\frac{n^2}{Bw})\)
剩下出现不超过 \(B\) 次的,将包含其的全部拿出来,每个二元组会贡献一个,这样就可以以时间为轴扫一遍,求答案,复杂度 \(\mathcal{O}(Bn)\)。
一种比较优秀的实现方法是将二元组挂到其中一个数上构成以时间为顺序的链表,这样还挺好写的。
平衡一下复杂度就是:\(\mathcal{O}(\frac{m\sqrt{m}}{\sqrt{w}})\)。
不过 bitset 的空间有点紧张,可以考虑将 \(\mathcal{O}(B)\) 个数分成 \(16\) 个块分别跑一遍,复杂度是一样的,常数稍微大点。
code
#include <iostream>
#include <bitset>
#include <vector>
using namespace std;
const int N = 1e6 + 20, B = 125;
#define fi first
#define se second
#define emp emplace_back
using pii = pair <int, int>;
int len[N], tot, id[N], _c[N];
bitset <N / B / 16 + 10> v[N];
vector <int> Q[N], S[N];
int a[N], b[N], c[N], ans[N], cnt[N];
signed main()
{
// freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
int m; cin >> m;
for (int i = 1; i <= m; i++) cin >> a[i] >> b[i] >> c[i];
for (int i = 1; i <= m; i++) if (a[i] == 1) cnt[c[i]]++;
for (int i = 1; i <= m; i++)
{
if (!cnt[i]) continue;
if (cnt[i] > B) id[i] = ++tot;
}
for (int w = N / B / 16, k; w - N / B / 16 < tot; w += N / B / 16)
{
k = w - N / B / 16, w = min(w, tot);
for (int i = 1; i <= m; i++) v[i].reset();
for (int i = 1; i <= m; i++)
{
if (a[i] == 1 && cnt[c[i]] > B && k < id[c[i]] && id[c[i]] <= w) v[b[i]][id[c[i]] - k] = 1;
if (a[i] == 2) ans[i] -= (v[b[i]] & v[c[i]]).count();
}
}
for (int i = 1; i <= m; i++)
{
if (a[i] == 1) ++_c[b[i]];
if (a[i] == 2)
{
if (b[i] != c[i]) ans[i] += _c[b[i]] + _c[c[i]];
else ans[i] = _c[b[i]];
}
}
for (int i = 1; i <= m; i++)
{
if (a[i] == 1)
{
if (!id[c[i]])
{
for (auto j : S[c[i]]) Q[b[i]].emp(j);
S[c[i]].emp(b[i]);
}
}
else
{
if (b[i] == c[i]) continue;
Q[c[i]].emp(-i);
Q[b[i]].emp(-i);
}
}
for (int i = 1; i <= m; i++) cnt[i] = 0;
for (int i = 1; i <= m; i++)
{
for (auto j : Q[i])
{
if (j > 0) ++cnt[j];
else ans[-j] -= i == b[-j] ? cnt[c[-j]] : cnt[b[-j]];
}
for (auto j : Q[i]) if (j > 0) cnt[j] = 0;
}
for (int i = 1; i <= m; i++) if (a[i] == 2) cout << ans[i] << '\n';
cerr << clock() / 1000.0 << '\n';
return 0;
}
P6619 [省选联考 2020 A/B 卷] 冰火战士
首先题目很迷惑,转化一下其实就是假设冰系战士温度小于等于 \(x\) 的战士能量和为 \(A\),火系战士温度大于等于 \(x\) 的战士能量和为 \(B\),求最大的 \(A + B - |A - B|\)。
有绝对值分讨一下,由于这个东西是单调的,所以线段树二分就可以了,然后树状数组维护一下 \(A, B\) 的值就好了。
复杂度 \(O(n\log n)\),常数有点大。
不建议看,写的有点迷惑
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 2e6 + 20;
// #define int unsigned
#define fi first
#define se second
#define emp emplace_back
using pii = pair <int, int>;
int tmp[N], a[N], b[N], c[N], d[N], m;
class SemTree
{
#define lid id << 1
#define rid id << 1 | 1
public :
int val[N << 2], tag[N << 2];
void Update(int id, int cl, int cr, int l, int r, int v)
{
if (l <= cl && cr <= r) return val[id] += v, tag[id] += v, void();
int mid = (cl + cr) >> 1;
if (tag[id])
{
val[lid] += tag[id], val[rid] += tag[id];
tag[lid] += tag[id], tag[rid] += tag[id];
tag[id] = 0;
}
if (l <= mid) Update(lid, cl, mid, l, r, v);
if (r > mid) Update(rid, mid + 1, cr, l, r, v);
val[id] = min(val[lid], val[rid]);
}
int Query(int id, int cl, int cr, int pos)
{
if (cl == cr) return val[id];
int mid = (cl + cr) >> 1;
if (tag[id])
{
val[lid] += tag[id], val[rid] += tag[id];
tag[lid] += tag[id], tag[rid] += tag[id];
tag[id] = 0;
}
if (pos <= mid) return Query(lid, cl, mid, pos);
else return Query(rid, mid + 1, cr, pos);
}
pii Find(int id, int cl, int cr, int k)
{
if (cl == cr) return {val[id], cl};
int mid = (cl + cr) >> 1;
if (tag[id])
{
val[lid] += tag[id], val[rid] += tag[id];
tag[lid] += tag[id], tag[rid] += tag[id];
tag[id] = 0;
}
if (val[rid] <= k) return Find(rid, mid + 1, cr, k);
else return Find(lid, cl, mid, k);
}
}T1;
class SemTree2
{
#define lid id << 1
#define rid id << 1 | 1
public :
pii val2[N << 2];
int tag2[N << 2];
void PushUp(int id)
{
val2[id] = max(val2[lid], val2[rid]);
}
void Build(int id, int cl, int cr)
{
if (cl == cr) return val2[id] = {0, cl}, void();
int mid = (cl + cr) >> 1;
Build(lid, cl, mid), Build(rid, mid + 1, cr);
PushUp(id);
}
void Update2(int id, int cl, int cr, int l, int r, int v)
{
if (l <= cl && cr <= r) return val2[id].fi += v, tag2[id] += v, void();
int mid = (cl + cr) >> 1;
if (tag2[id])
{
val2[lid].fi += tag2[id], val2[rid].fi += tag2[id];
tag2[lid] += tag2[id], tag2[rid] += tag2[id];
tag2[id] = 0;
}
if (l <= mid) Update2(lid, cl, mid, l, r, v);
if (r > mid) Update2(rid, mid + 1, cr, l, r, v);
val2[id] = max(val2[lid], val2[rid]);
}
pii Query2(int id, int cl, int cr, int l, int r)
{
if (l <= cl && cr <= r) return val2[id];
if (tag2[id])
{
val2[lid].fi += tag2[id], val2[rid].fi += tag2[id];
tag2[lid] += tag2[id], tag2[rid] += tag2[id];
tag2[id] = 0;
}
int mid = (cl + cr) >> 1;
pii ans = {0, 0};
if (l <= mid) ans = Query2(lid, cl, mid, l, r);
if (r > mid) ans = max(ans, Query2(rid, mid + 1, cr, l, r));
return ans;
}
}T2;
class SemTree3
{
public :
int c[N];
void Update(int pos, int v)
{
for (int i = pos; i <= tmp[0]; i += i & -i) c[i] += v;
}
int Query(int pos)
{
int ans = 0;
for (int i = pos; i; i -= i & -i) ans += c[i];
return ans;
}
}T3;
signed main()
{
// freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> m;
for (int i = 1; i <= m; i++)
{
int op; cin >> op; a[i] = op;
if (op == 1) cin >> b[i] >> c[i] >> d[i], tmp[++tmp[0]] = c[i];
else cin >> b[i];
}
sort(tmp + 1, tmp + 1 + tmp[0]);
tmp[0] = unique(tmp + 1, tmp + 1 + tmp[0]) - (tmp + 1);
T2.Build(1, 1, tmp[0]);
for (int i = 1; i <= m; i++)
{
int op = a[i], id = i;
if (op == 1)
{
c[id] = lower_bound(tmp + 1, tmp + 1 + tmp[0], c[id]) - tmp;
if (!b[id])
{
T1.Update(1, 1, tmp[0], c[id], tmp[0], d[id]);
T3.Update(c[id], d[id] + d[id]);
}
else
{
T1.Update(1, 1, tmp[0], 1, c[id], -d[id]);
T2.Update2(1, 1, tmp[0], 1, c[id], d[id] + d[id]);
}
}
else
{
id = b[id];
if (!b[id])
{
T1.Update(1, 1, tmp[0], c[id], tmp[0], -d[id]);
T3.Update(c[id], -d[id] - d[id]);
}
else
{
T1.Update(1, 1, tmp[0], 1, c[id], d[id]);
T2.Update2(1, 1, tmp[0], 1, c[id], -d[id] - d[id]);
}
}
pii _s = T1.Find(1, 1, tmp[0], 0);
if (_s.fi > 0) --_s.se;
pii ans = max(_s.se > 0 ? make_pair(T3.Query(_s.se), _s.se) : make_pair(0, 0), _s.se <= tmp[0] ? T2.Query2(1, 1, tmp[0], _s.se + 1, tmp[0]) : make_pair(0, 0));
if (ans.fi == 0) cout << "Peace\n";
else cout << tmp[ans.se] << ' ' << ans.fi << '\n';
}
return 0;
}
P8078 [WC2022] 秃子酋长
板。
因为这是个区间问题还不强制在线,这个信息还不好维护,自然想用莫队。
但是加入一个点很麻烦的哦,无法 \(\mathcal{O}(1)\) 找前驱后继啊。
所以理所应当用回滚莫队咯,因为链表是一种删除简单,加入困难的数据结构。
删除时直接更新前驱后继和答案就行,很简单。
复杂度 \(\mathcal{O}(n\sqrt{n})\),常数略大。
双 log 做法我不会(
code
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 500000 + 20, B = 1000;
// #define int unsigned
#define fi first
#define se second
#define emp emplace_back
using pii = pair <int, int>;
int n, m, a[N], pos[N], st[N], en[N], be[N], pre[N], nxt[N], top, mi, ma;
long long res[N], ans;
pair <bool, pii> sta[N];
struct Node
{
int l, r, id;
}q[N];
inline int Div(int x, int y)
{
if (!x || !y) return 0;
return abs(x - y);
}
inline void Del(int x)
{
ans -= abs(pos[x] - pos[pre[x]]) + abs(pos[x] - pos[nxt[x]]) - abs(pos[nxt[x]] - pos[pre[x]]);
if (x == mi) mi = nxt[x];
if (x == ma) ma = pre[x];
nxt[pre[x]] = nxt[x], pre[nxt[x]] = pre[x];
}
inline void _Del(int x)
{
sta[++top] = {1, {pre[x], nxt[pre[x]]}}, sta[++top] = {0, {nxt[x], pre[nxt[x]]}};
ans -= abs(pos[x] - pos[pre[x]]) + abs(pos[x] - pos[nxt[x]]) - abs(pos[nxt[x]] - pos[pre[x]]);
if (x == mi) mi = nxt[x];
if (x == ma) ma = pre[x];
nxt[pre[x]] = nxt[x], pre[nxt[x]] = pre[x];
}
signed main()
{
// freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> a[i], pos[a[i]] = i;
for (int i = 1; i <= m; i++) cin >> q[i].l >> q[i].r, q[i].id = i;
for (int i = 1; i <= n; i++) be[i] = (i - 1) / B + 1, en[be[i]] = i;
for (int i = 1; i <= n; i++) st[i] = en[i - 1] + 1;
sort(q + 1, q + 1 + m, [](Node x, Node y){return (be[x.l] ^ be[y.l]) ? be[x.l] < be[y.l] : x.r > y.r;});
for (int i = 1, LS = n; i <= m; i++)
{
long long b = be[q[i].l], _ans = 0;
if (be[q[i].l] != be[q[i - 1].l]) // clear
{
ans = 0, LS = n, mi = ma = 0;
int ls = 0;
for (int i = 1; i <= m; i++) pre[i] = nxt[i] = i;
for (int i = 1; i <= n; i++)
{
if (pos[i] < st[b]) continue;
if (!mi) mi = i;
ans += abs(pos[i] - pos[ls]);
pre[i] = ls, nxt[ls] = i, ls = i;
}
nxt[ma = ls] = n + 1;
ans += pos[ls];
}
for (int j = LS; j > q[i].r; j--) Del(a[j]);
int _mi = mi, _ma = ma;
LS = q[i].r, _ans = ans;
for (int j = st[b]; j < q[i].l; j++) _Del(a[j]);
ans -= pos[ma] + pos[mi];
while (top)
{
pii w = sta[top].se;
(sta[top].fi ? nxt[w.fi] : pre[w.fi]) = w.se, --top;
}
res[q[i].id] = ans, ans = _ans, mi = _mi, ma = _ma;
}
for (int i = 1; i <= m; i++) cout << res[i] << '\n';
return 0;
}
恭喜进入 NOI/NOI+/CTSC 区域。
P4278 带插入区间K小值
比较简单。
首先不带插入是简单的,可以用分块维护。
但是带上插入就需要使用链表等技术了,不过传统的链表不适合随机访问和信息合并。
这时候需要块状链表了,完美解决了链表不适合随机访问和信息合并的问题,还能用来替换分块。
关于块状链表:大概就是在一个块中插入元素,如果这个块过大,就折半分裂。
具体做法:先值域分块,在块状链表中维护每个值域块的前缀出现次数,和每个值的前缀出现次数,空间复杂度 \(\mathcal{O}(n\sqrt{n})\)。
这样子区间第 \(k\) 小只需要找到在哪个块内,然后在块内在统计即可。
时间复杂度 \(\mathcal{O}(n \sqrt{n})\),常数略大。
code
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 70000 + 10, B = 1500;
#define emp emplace_back
class Blocks
{
public :
int siz, l, r, nxt, pre;
int value[B + 3];
int sum_b[B + 100], sum[N];
void Split();
void Check();
void Insert(int, int);
void Change(int, int);
}blocks[B + 100];
int block_tot, bel_tot;
int tmp_sum[N], tmp_sum_b[N], bel[N], st[N], en[N];
void Blocks::Split()
{
blocks[++block_tot].nxt = nxt, nxt = block_tot;
int md = siz >> 1, _siz = siz;
Blocks &bl = blocks[nxt];
memcpy(bl.sum, sum, sizeof(sum));
for (int i = 1; i <= bel_tot; i++) bl.sum_b[i] = sum_b[i];
for (int i = md, w = value[i]; i < _siz; i++, w = value[i])
sum_b[bel[w]]--, sum[w]--, --siz, bl.value[bl.siz++] = w;
r = l + siz - 1, bl.l = r + 1, bl.r = r + bl.siz;
}
void Blocks::Check() {if (siz > B) Split();}
void Blocks::Insert(int x, int pos)
{
pos -= l;
for (int i = siz - 1; i >= pos; i--) swap(value[i], value[i + 1]);
value[pos] = x, ++siz;
sum_b[bel[x]]++, sum[x]++, r++;
int qw = nxt;
while (qw)
{
Blocks &bl = blocks[qw];
bl.sum_b[bel[x]]++, bl.sum[x]++, bl.l++, bl.r++;
qw = blocks[qw].nxt;
}
Check();
}
void Blocks::Change(int x, int pos)
{
pos -= l;
int lx = value[pos];
value[pos] = x;
sum_b[bel[lx]]--, sum[lx]--;
sum_b[bel[x]]++, sum[x]++;
int qw = nxt;
while (qw)
{
Blocks &bl = blocks[qw];
bl.sum_b[bel[lx]]--, bl.sum[lx]--;
bl.sum_b[bel[x]]++, bl.sum[x]++;
qw = bl.nxt;
}
}
signed main()
{
// freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
for (int i = 0; i < N; i++) bel[i] = (i - 1) / B + 1, bel_tot = bel[i], en[bel_tot] = i;
for (int i = 1; i < N; i++) st[i] = en[i - 1] + 1;
st[1] = 0; int n, q; cin >> n;
blocks[++block_tot].l = 1;
for (int i = 1, x; i <= n; i++) cin >> x, blocks[block_tot].Insert(x, i);
cin >> q;
int lastans = 0;
for (int i = 1; i <= q; i++)
{
char ch; cin >> ch;
int x, y; cin >> x >> y;
x ^= lastans, y ^= lastans;
if (ch == 'Q')
{
int k; cin >> k;
k ^= lastans;
int L = 1, R = 0;
for (int i = 1, br = 0, pr = 0; i && !br; pr = i, i = blocks[i].nxt)
{
if (blocks[i].l <= x && x <= blocks[i].r)
for (int j = x - blocks[i].l; j <= blocks[i].r - blocks[i].l && !br; j++)
tmp_sum[blocks[i].value[j]]++, tmp_sum_b[bel[blocks[i].value[j]]]++, br = j >= y - blocks[i].l, L = i;
else if (blocks[i].l <= y && y <= blocks[i].r)
for (int j = 0; j <= y - blocks[i].l; j++)
tmp_sum[blocks[i].value[j]]++, tmp_sum_b[bel[blocks[i].value[j]]]++, R = pr;
}
int w = 0;
for (int i = 1; i <= bel_tot; i++)
{
w += max(0, blocks[R].sum_b[i] - blocks[L].sum_b[i]) + tmp_sum_b[i];
if (w >= k)
{
w -= max(0, blocks[R].sum_b[i] - blocks[L].sum_b[i]) + tmp_sum_b[i];
for (int j = st[i]; j <= en[i]; j++)
{
w += max(0, blocks[R].sum[j] - blocks[L].sum[j]) + tmp_sum[j];
if (w >= k)
{
cout << (lastans = j) << '\n';
for (int i = 1, br = 0; i && !br; i = blocks[i].nxt)
{
if (blocks[i].l <= x && x <= blocks[i].r)
for (int j = x - blocks[i].l; j <= blocks[i].r - blocks[i].l && !br; j++)
tmp_sum[blocks[i].value[j]]--, tmp_sum_b[bel[blocks[i].value[j]]]--, br = j >= y - blocks[i].l;
else if (blocks[i].l <= y && y <= blocks[i].r)
for (int j = 0; j <= y - blocks[i].l; j++)
tmp_sum[blocks[i].value[j]]--, tmp_sum_b[bel[blocks[i].value[j]]]--;
}
break;
}
}
break;
}
}
}
else for (int i = 1, suc = 0; i && !suc; i = blocks[i].nxt)
if (x <= blocks[i].r + (ch == 'I')) (ch == 'M' ? blocks[i].Change(y, x) : blocks[i].Insert(y, x)), suc = 1;
}
return 0;
}
P5210 [ZJOI2017] 线段树
很奇妙啊。
手模几组情况,发现一个有趣的事情:
选定 \(l, r\) 那么假设对应到树上是 \(L, R\),先只考虑 \(r = n\) 的情况。
称作为父节点的左儿子叫做右。
那么从 \(L\) 开始向上走,如果一直是向右的话,由于 \(L\) 是叶子,会发现产生贡献的节点永远只有最上面的那个,而一旦产生了向左走的情况,这种现象就消失了。
结论一:从 \(L\) 向上的极长右链只有其链顶产生贡献。
现在假设出现了一个向左走的情况了,那么这个节点的左儿子不在区间内;如果再往后出现了一个向右走的情况,那么其右儿子在区间内。
结论二:除了那个极长的右链外,其他右链上的节点的右儿子都会产生贡献。
算上 \(R\) 呢?
其实在到达 \(LCA(L, R)\) 之前没有区别,而 \(R\) 的贡献就是左右翻转。
这里要特判满区间的情况。
那怎么维护呢?这里以 \(L\) 的维护为例。
由于贡献是到达 \(u\) 的距离,首先可以将 \(L\) 到 \(LCA(L, R)\) 的路径分为两种,一种是那个极长右链,另一个是剩下的。
极长右链到哪可以预处理,然后变成一个链上的所有点和另一个点的距离和。
- \(u\) 在链上某个节点的子树中,那么找到这个节点分讨向下的贡献和向上的贡献即可,需要维护一个所有父亲到这个点的距离和还有根链到达根节点的距离和,有点恶心哦。
- \(u\) 在链顶子树之外,直接算给链顶就好啦。
另一部分咋办呀。
首先做一个和刚才一样的分讨。
其实就是将之前维护的东西变成右链上的节点的右儿子到某个节点的距离和就行。
需要注意的是:如果 \(u\) 在某个有贡献的点的子树内时需要将贡献减 \(2\)。
时间复杂度 \(O(n\log n)\),常数有点大。
不建议看,看了你也看不懂
#include <iostream>
using namespace std;
const int N = 4e5 + 10;
#define int long long
int n, m, tot;
int topL[N], topR[N], top[N], ls[N], rs[N], f[N], siz[N], son[N], dep[N], id[N], L[N], R[N];
int sumL[N], totL[N], sumR[N], totR[N], toL[N], toR[N];
void dfs(int x)
{
topL[x] = x == ls[f[x]] ? topL[f[x]] : x, topR[x] = x == rs[f[x]] ? topR[f[x]] : x;
sumL[x] = x == ls[f[x]] ? sumL[f[x]] + dep[f[x]] + 1 : sumL[f[x]], sumR[x] = x == rs[f[x]] ? sumR[f[x]] + dep[f[x]] + 1 : sumR[f[x]];
totL[x] = totL[f[x]] + (x == ls[f[x]]), totR[x] = totR[f[x]] + (x == rs[f[x]]);
toL[x] = toL[f[x]] + totL[x] + (x == ls[f[x]]), toR[x] = toR[f[x]] + totR[x] + (x == rs[f[x]]);
if (ls[x]) f[ls[x]] = x, dep[ls[x]] = dep[x] + 1, dfs(ls[x]), siz[x] += siz[ls[x]];
if (rs[x]) f[rs[x]] = x, dep[rs[x]] = dep[x] + 1, dfs(rs[x]), siz[x] += siz[rs[x]];
siz[x]++, son[x] = siz[ls[x]] > siz[rs[x]] ? ls[x] : rs[x];
}
void dfs2(int x, int fa)
{
top[x] = fa;
if (son[x]) dfs2(son[x], fa);
if (ls[x] && ls[x] != son[x]) dfs2(ls[x], ls[x]);
if (rs[x] && rs[x] != son[x]) dfs2(rs[x], rs[x]);
}
int LCA(int x, int y)
{
while (top[x] != top[y])
{
if (dep[top[x]] < dep[top[y]]) swap(x, y);
x = f[top[x]];
}
return dep[x] < dep[y] ? x : y;
}
int Dis(int x, int y)
{
return dep[x] + dep[y] - 2 * dep[LCA(x, y)];
}
int Build(int l, int r)
{
int _id = ++tot;
if (l == r) return L[_id] = l, R[_id] = r, id[l] = _id;
int mid; cin >> mid;
ls[_id] = Build(l, mid), rs[_id] = Build(mid + 1, r);
L[_id] = L[ls[_id]], R[_id] = R[rs[_id]];
return _id;
}
int ProdL(int x, int y, int u)
{
int v = LCA(x, u);
int ans = dep[x] + dep[u] - 2 * dep[v];
if (dep[v] < dep[y]) v = y;
bool op = v != x && rs[v] && LCA(rs[v], u) == rs[v];
int d = Dis(u, v);
ans += sumL[x] - sumL[v] + (d - dep[v]) * (totL[x] - totL[v]);
ans += toL[v] - toL[y] - totL[y] * (dep[v] - dep[y]) + (totL[v] - totL[y]) * d;
return ans - 2 * op;
}
int ProdR(int x, int y, int u)
{
int v = LCA(x, u);
int ans = dep[x] + dep[u] - 2 * dep[v];
if (dep[v] < dep[y]) v = y;
bool op = v != x && ls[v] && LCA(ls[v], u) == ls[v];
int d = Dis(u, v);
ans += sumR[x] - sumR[v] + (d - dep[v]) * (totR[x] - totR[v]);
ans += toR[v] - toR[y] - totR[y] * (dep[v] - dep[y]) + (totR[v] - totR[y]) * d;
return ans - 2 * op;
}
signed main()
{
// freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n;
int rt = Build(1, n);
dfs(rt), dfs2(rt, rt), cin >> m;
for (int i = 1; i <= m; i++)
{
int u, l, r;
cin >> u >> l >> r;
int lca = LCA(id[l], id[r]);
int lcaL = topL[id[l]], lcaR = topR[id[r]];
if (dep[lcaL] <= dep[lca]) lcaL = ls[lca];
if (dep[lcaR] <= dep[lca]) lcaR = rs[lca];
if (lcaL == ls[lca] && lcaR == rs[lca]) // all
cout << Dis(lca, u) << '\n';
else
cout << ProdL(lcaL, ls[lca], u) + ProdR(lcaR, rs[lca], u) << '\n';
}
}
P5298 [PKUWC2018] Minimax
有点神秘了。
先感受一下题目应该和这个意义不明的计算答案式子无关。
设 \(dp_{x, i}\) 表示节点 \(x\) 取到第 \(i\) 小的概率。
假设 \(s_{x, j} = \sum_{i = 0}^{j}{dp_{x, i}}\) 。
化简化简就成了:
发现一件事,就是如果在线段树合并中加入前缀查询,那么就可以完美转移了,于是时间复杂度是 \(O(n\log^2 n)\) 或 \(O(n\log n)\),常数很大。
有一个 TopTree 做法,计算概率的前缀和(用 0/1 转移)。
不太好看
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 3e5 + 10, mod = 998244353;
#define int long long
int n, m;
int qpow(int x, int b)
{
int res = 1;
while (b)
{
if (b & 1) res = res * x % mod;
x = x * x % mod;
b >>= 1;
}
return res;
}
class SemTree
{
public :
int sum[N << 3], tag[N << 3], ls[N << 3], rs[N << 3];
int qu[N], tot, qutot, bin[N << 3], bintot;
void PushUp(int id)
{
sum[id] = (sum[ls[id]] + sum[rs[id]]) % mod;
}
int New()
{
if (bintot) return bin[bintot--];
return ++tot;
}
void Del(int x)
{
bin[++bintot] = x;
sum[x] = tag[x] = ls[x] = rs[x] = 0;
}
void PushDown(int id)
{
if (tag[id] != 1)
{
(tag[ls[id]] *= tag[id]) %= mod, (tag[rs[id]] *= tag[id]) %= mod;
(sum[ls[id]] *= tag[id]) %= mod, (sum[rs[id]] *= tag[id]) %= mod;
tag[id] = 1;
}
}
void Insert(int &id, int cl, int cr, int pos, int v)
{
if (!id) id = New(), tag[id] = 1;
if (cl == cr) return sum[id] = v, void();
int mid = (cl + cr) >> 1;
if (pos <= mid) Insert(ls[id], cl, mid, pos, v);
else Insert(rs[id], mid + 1, cr, pos, v);
PushUp(id);
}
int Query(int id, int cl, int cr, int l, int r)
{
if (!id) return 0;
if (l <= cl && cr <= r) return sum[id];
PushDown(id);
int mid = (cl + cr) >> 1, ans = 0;
if (l <= mid) ans = Query(ls[id], cl, mid, l, r);
if (r > mid) ans = (ans + Query(rs[id], mid + 1, cr, l, r)) % mod;
return ans;
}
void PreMerge(int rx, int ry, int x, int y, int l, int r, int px)
{
if (!x || !y)
{
if (!x && !y) return ;
if (!y) qu[++qutot] = (Query(ry, 1, m, 1, r ) * (2 * px % mod - 1 + mod) % mod + (1 - px + mod) * Query(ry, 1, m, 1, m) % mod) % mod;
if (!x) qu[++qutot] = (Query(rx, 1, m, 1, r - 1) * (2 * px % mod - 1 + mod) % mod + (1 - px + mod) * Query(rx, 1, m, 1, m) % mod) % mod;
return ;
}
PushDown(x), PushDown(y);
if (l == r)
{
int sls = Query(rx, 1, m, 1, l - 1), srs = Query(ry, 1, m, 1, l - 1);
int dls = sum[x], drs = sum[y];
int als = Query(rx, 1, m, 1, m), ars = Query(ry, 1, m, 1, m);
qu[++qutot] = ((2 * px - 1) % mod * (dls * srs % mod + drs * sls % mod) % mod + (1 - px + mod) * (dls * ars % mod + drs * als % mod) % mod) % mod;
return ;
}
int mid = (l + r) >> 1;
PreMerge(rx, ry, ls[x], ls[y], l, mid, px), PreMerge(rx, ry, rs[x], rs[y], mid + 1, r, px);
}
signed Merge(signed x, signed y, signed l, signed r)
{
if (!x || !y)
{
if (!x && !y) return 0;
int v = qu[++qutot];
(sum[x | y] *= v) %= mod;
if (v) (tag[x | y] *= v) %= mod;
return x | y;
}
if (l == r)
{
sum[x] = qu[++qutot];
Del(y);
return x;
}
int mid = (l + r) >> 1;
ls[x] = Merge(ls[x], ls[y], l, mid);
rs[x] = Merge(rs[x], rs[y], mid + 1, r);
PushUp(x), Del(y);
return x;
}
}T;
int ls[N], rs[N], f[N], p[N], inv, rt[N], t;
void dfs(int x)
{
if (ls[x] || rs[x]) p[x] = p[x] * inv % mod;
else T.Insert(rt[x], 1, m, p[x], 1);
if (ls[x]) dfs(ls[x]);
if (rs[x]) dfs(rs[x]);
if (ls[x] && !rs[x]) rt[x] = rt[ls[x]];
if (ls[x] && rs[x])
{
T.qutot = 0;
T.PreMerge(rt[ls[x]], rt[rs[x]], rt[ls[x]], rt[rs[x]], 1, m, p[x]);
T.qutot = 0;
rt[x] = T.Merge(rt[ls[x]], rt[rs[x]], 1, m);
}
}
int tmp[N];
signed main()
{
// freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
inv = qpow(10000, mod - 2);
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> f[i];
if (!f[i]) continue;
if (ls[f[i]]) rs[f[i]] = i;
else ls[f[i]] = i;
}
for (int i = 1; i <= n; i++) cin >> p[i];
for (int i = 1; i <= n; i++) if (!ls[i] && !rs[i]) tmp[++m] = p[i];
sort(tmp + 1, tmp + 1 + m);
m = unique(tmp + 1, tmp + 1 + m) - (tmp + 1);
for (int i = 1; i <= n; i++) if (!ls[i] && !rs[i]) p[i] = lower_bound(tmp + 1, tmp + 1 + m, p[i]) - tmp;
dfs(1);
int ans = 0;
for (int i = 1; i <= m; i++)
{
int d = T.Query(rt[1], 1, m, i, i);
ans += d * d % mod * i % mod * tmp[i] % mod;
}
cout << ans % mod << '\n';
return 0;
}
P5608 [Ynoi2013] 文化课
变态题啊。
先确定使用线段树。
要想做区间赋值,一定需要维护区间多项式,但是区间多项式如何合并啊。
此时我们发现,区间本质不同的多项式项数最多只有 \(O(\sqrt n)\) 种,所以合并可以暴力归并!这样复杂度是 \(\sum_{k = 0}^{\log n}{O(\sqrt{\frac{n}{2^k}})} = O(\sqrt n)\) 的。
但是区间赋值的时候如果暴力修改还是会变成 \(O(\sqrt n\log n)\)(快速幂)。
但是如果每次计算继承上一个的值,也就是计算 \(x^{a_i - a_{i - 1}}\) 而不是 \(x^{a_i}\),算一下复杂度就是 \(\sum_i{\log{a_{i} - a_{i - 1}}} = \log{\prod_i{a_i - a_{i - 1}}} \le \log n\),一些复杂的证明放缩可以得到这部分复杂度是 \(O(n\sqrt n)\)(不会整,反正起码是有 \(\log^2 n\) 兜底)。
然后区间改符号就是直接多项式推平就好。
口胡部分就结束了,代码细节超级超级多。
两个区间之间如果是乘号链接需要合并中间两个项,为更新贡献需要维护左连续乘积和右连续乘积。
如果这个区间全都是乘法,那么需要将贡献跨区间。
如果区间推成全加或全乘,需要维护区间乘积和区间和。
还有一些需要维护的,不多赘述,那些就比较显而易见了。
时间复杂度 \(\mathcal{O}(n\sqrt{n})\),常数爆炸。
实现有点粗糙
#include <iostream>
#include <algorithm>
#include <vector>
#include <map>
#include <cmath>
#include <queue>
using namespace std;
const int N = 1e5 + 10, mod = 1e9 + 7;
#define int long long
#define fi first
#define se second
int qpow(int x, int b)
{
int res = 1;
while (b)
{
if (b & 1) res = res * x % mod;
x = x * x % mod;
b >>= 1;
}
return res;
}
struct Data
{
int sl, sr, sum;
bool am;
Data(int _sl, int _sr, int _sum, bool _am)
{
sl = _sl, sr = _sr, sum = _sum, am = _am;
}
Data() {sl = sr = sum = 0; am = 0;}
friend Data operator + (const Data &x, const Data &y)
{
Data tmp;
tmp.sl = x.sl, tmp.sr = y.sr;
tmp.sum = (x.sum + y.sum) % mod;
tmp.am = 0;
return tmp;
}
friend Data operator * (const Data &x, const Data &y)
{
Data tmp;
tmp.sl = x.sl, tmp.sr = y.sr;
tmp.sum = (x.sum + y.sum) % mod;
if (x.am) tmp.sl = tmp.sl * y.sl % mod;
if (y.am) tmp.sr = tmp.sr * x.sr % mod;
tmp.am = x.am && y.am;
(tmp.sum += (x.sr * y.sl % mod - x.sr - y.sl + mod + mod)) %= mod;
return tmp;
}
};
int n, m, num[N], OP[N];
const int M = N << 2;
class SemTree
{
#define lid id << 1
#define rid id << 1 | 1
public :
vector<pair<signed, signed>> poly[M];
int ls[M], rs[M], siz[M], L[M], R[M], len[M];
int tag[M], tagOP[M], sum[M], sl[M], sr[M], sigma[M], prod[M];
bool op[M], am[M];
void PushUp(int id)
{
sum[id] = (sum[lid] + sum[rid]) % mod;
sigma[id] = (sigma[lid] + sigma[rid]) % mod;
prod[id] = prod[lid] * prod[rid] % mod;
L[id] = L[lid], R[id] = R[rid], am[id] = am[lid] && am[rid] && op[id];
if (op[id]) (sum[id] += sr[lid] * sl[rid] % mod - sr[lid] - sl[rid] + mod + mod) %= mod;
ls[id] = ls[lid], rs[id] = rs[rid];
sl[id] = sl[lid], sr[id] = sr[rid];
if (am[lid] && op[id]) ls[id] += ls[rid], sl[id] = sl[id] * sl[rid] % mod;
if (am[rid] && op[id]) rs[id] += rs[lid], sr[id] = sr[id] * sr[lid] % mod;
if (sum[id] < 0) cerr << "??";
}
void Merge(int id)
{
int fl = rs[lid], fr = ls[rid], fm = rs[lid] + ls[rid];
siz[id] = 0, PushUp(id);
int pl = 1, pr = 1;
while (pl <= siz[lid] || pr <= siz[rid])
{
pair<int, int> tmp = {0, 0};
if ((pl <= siz[lid] && poly[lid][pl].fi < poly[rid][pr].fi) || pr > siz[rid])
{
tmp = poly[lid][pl];
if (op[id] && poly[lid][pl].fi == fl) fl = 0, tmp.se--;
++pl;
}
else
{
tmp = poly[rid][pr];
if (op[id] && poly[rid][pr].fi == fr) fr = 0, tmp.se--;
++pr;
}
if (!tmp.se) continue;
if (tmp.fi != poly[id][siz[id]].fi) poly[id][++siz[id]] = tmp;
else poly[id][siz[id]].se += tmp.se;
}
if (op[id])
{
for (int i = 1; i <= siz[id]; i++)
{
if (fm == poly[id][i].fi) {poly[id][i].se++; break;}
if (fm < poly[id][i].fi)
{
for (int j = siz[id]; j >= i; j--) swap(poly[id][j], poly[id][j + 1]);
poly[id][i] = {fm, 1}, ++siz[id];
break;
}
}
if (fm > poly[id][siz[id]].fi) poly[id][++siz[id]] = {fm, 1};
}
}
void Upd(int id, int TAGOP)
{
if (len[id] == 1) return ;
op[id] = TAGOP - 1, tagOP[id] = TAGOP;
if (TAGOP == 1) sum[id] = sigma[id], sl[id] = L[id], sr[id] = R[id], ls[id] = rs[id] = 1, siz[id] = 1, am[id] = 0, poly[id][1] = {1, len[id]};
if (TAGOP == 2) sum[id] = sl[id] = sr[id] = prod[id], ls[id] = rs[id] = len[id], siz[id] = 1, am[id] = 1, poly[id][1] = {len[id], 1};
}
void UpdNum(int id, int x)
{
int lm = 1, lsum = 0;
int fl = 0;
for (int i = 1; i <= siz[id]; i++)
{
lm = lm * qpow(x, poly[id][i].fi - poly[id][i - 1].fi) % mod;
if (poly[id][i].fi == ls[id]) sl[id] = lm, fl++;
if (poly[id][i].fi == rs[id]) sr[id] = lm, fl++;
lsum += lm * (int)poly[id][i].se % mod;
}
prod[id] = qpow(x, len[id]), sigma[id] = len[id] * x % mod;
L[id] = R[id] = x, sum[id] = lsum % mod, tag[id] = x;
}
void PushDown(int id)
{
if (tagOP[id]) Upd(lid, tagOP[id]), Upd(rid, tagOP[id]), tagOP[id] = 0;
if (tag[id]) UpdNum(lid, tag[id]), UpdNum(rid, tag[id]), tag[id] = 0;
}
void Build(int id, int cl, int cr)
{
if (cl == cr) return sum[id] = prod[id] = L[id] = R[id] = sigma[id] = sl[id] = sr[id] = num[cl],
poly[id].resize(3), len[id] = ls[id] = rs[id] = siz[id] = 1, am[id] = 1, poly[id][1] = {1, 1}, void();
int mid = (cl + cr) >> 1;
this->op[id] = OP[mid];
Build(lid, cl, mid), Build(rid, mid + 1, cr);
len[id] = len[lid] + len[rid];
poly[id].resize(sqrt(4 * len[id]) + 4);
Merge(id);
}
void Update(int id, int cl, int cr, int l, int r, int x)
{
if (l <= cl && cr <= r) return UpdNum(id, x), void();
PushDown(id);
int mid = (cl + cr) >> 1;
if (l <= mid) Update(lid, cl, mid, l, r, x);
if (r > mid) Update(rid, mid + 1, cr, l, r, x);
PushUp(id);
}
void UpdateTag(int id, int cl, int cr, int l, int r, bool op)
{
if (l <= cl && cr <= r) return Upd(id, op + 1), void();
PushDown(id);
int mid = (cl + cr) >> 1;
if (l <= mid) UpdateTag(lid, cl, mid, l, r, op);
if (r > mid) UpdateTag(rid, mid + 1, cr, l, r, op);
if (l <= mid && mid < r) this->op[id] = op;
Merge(id);
}
Data Query(int id, int cl, int cr, int l, int r)
{
if (l <= cl && cr <= r) return Data(sl[id], sr[id], sum[id], am[id]);
int mid = (cl + cr) >> 1;
PushDown(id);
if (r <= mid) return Query(lid, cl, mid, l, r);
else if (l > mid) return Query(rid, mid + 1, cr, l, r);
return op[id] ? (Query(lid, cl, mid, l, r) * Query(rid, mid + 1, cr, l, r))
: (Query(lid, cl, mid, l, r) + Query(rid, mid + 1, cr, l, r));
}
}T;
signed main()
{
// freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> num[i], num[i] %= mod;
for (int i = 1; i < n; i++) cin >> OP[i];
T.Build(1, 1, n);
for (int i = 1; i <= m; i++)
{
int op; cin >> op;
if (op == 1)
{
int l, r, x; cin >> l >> r >> x;
x %= mod;
T.Update(1, 1, n, l, r, x);
}
if (op == 2)
{
int l, r, x; cin >> l >> r >> x;
T.UpdateTag(1, 1, n, l, r + 1, x);
}
if (op == 3)
{
int l, r; cin >> l >> r;
cout << T.Query(1, 1, n, l, r).sum << '\n';
}
}
return 0;
}
P7881 [Ynoi2006] rmpq
人民赔钱(rmpq)。
每个修改会将平面划分成两块,一种想法就是维护平面内所有不同值的块。
不过这样是 \(O(n^2)\) 的。
神奇的思路就是先分块,然后分治合并,复杂度竟然是:
也就是 \(O(n\sqrt{n})\) 了。
合并是很暴力的归并,标记一下原来分别属于那个块就可以了。
不过分治合并不好做到实时查询,可以写成二进制分组的形式,和分治合并是等价的。
查询就在这 \(O(\sqrt n)\) 个块中查询合并起来就好啦。
值得注意的是这 \(O(n)\) 的块的坐标是离散的,所以查询需要带一个 \(\log n\),或者写待插入分散层叠去掉这个老哥。
时间复杂度是 \(\mathcal{O}(n\sqrt n \log n)\) 或 \(\mathcal{O}(n\sqrt n)\),跑的挺快的。
code
struct Data{
unsigned short a,b,c,d;
void operator*=(const Data &x);
void clr();
};
void update(int x,int dim,Data d1,Data d2);
Data query(int x,int y);
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
#define emp emplace_back
const int B = 256;
class State
{
public :
vector<int> r, c;
vector<Data> num;
int rs, cs;
friend State operator + (const State &x, const State &y)
{
State res;
res.cs = res.rs = 1;
vector<int> lr1, lr2, lr;
for (int p1 = 0, p2 = 0; p1 < x.rs || p2 < y.rs;)
{
if (p1 < x.rs && (p2 >= y.rs || x.r[p1] <= y.r[p2]))
{
if (p2 < y.rs && x.r[p1] == y.r[p2]) ++p2;
lr1.emp(p1), lr2.emp(p2 - 1);
lr.emp(x.r[p1]), ++p1;
}
else
{
lr1.emp(p1 - 1), lr2.emp(p2);
lr.emp(y.r[p2]), ++p2;
}
res.rs = lr1.size();
}
vector<int> lc1, lc2, lc;
for (int p1 = 0, p2 = 0; p1 < x.cs || p2 < y.cs;)
{
if (p1 < x.cs && (p2 >= y.cs || x.c[p1] <= y.c[p2]))
{
if (p2 < y.cs && x.c[p1] == y.c[p2]) ++p2;
lc1.emp(p1), lc2.emp(p2 - 1);
lc.emp(x.c[p1]), ++p1;
}
else
{
lc1.emp(p1 - 1), lc2.emp(p2);
lc.emp(y.c[p2]), ++p2;
}
res.cs = lc1.size();
}
res.c = lc, res.r = lr;
res.num.resize(res.rs * res.cs);
for (int i = 0; i < res.rs; i++)
{
for (int j = 0; j < res.cs; j++)
{
res.num[i * res.cs + j] = x.num[lr1[i] * x.cs + lc1[j]];
res.num[i * res.cs + j] *= y.num[lr2[i] * y.cs + lc2[j]];
}
}
return res;
}
Data Find(int x, int y)
{
return num[(upper_bound(r.begin(), r.end(), x) - r.begin() - 1) * cs + (upper_bound(c.begin(), c.end(), y) - c.begin() - 1)];
}
State(int x, int dim, Data d1, Data d2)
{
if (dim)
{
c.emp(1), r.emp(1), c.emp(x);
num.resize(2), num[0] = d1, num[1] = d2;
}
else
{
c.emp(1), r.emp(1), r.emp(x);
num.resize(2), num[0] = d1, num[1] = d2;
}
rs = r.size(), cs = c.size();
}
State() {}
~State() {r.clear(); c.clear(); rs = cs = 0;}
};
vector <State> q;
void update(int x, int dim, Data d1, Data d2)
{
q.emp(State(x, dim, d1, d2));
while (q.size() > 1u && q[q.size() - 1].rs + q[q.size() - 1].cs < B &&
q[q.size() - 1].rs + q[q.size() - 1].cs >= q[q.size() - 2].rs + q[q.size() - 2].cs)
q[q.size() - 2] = q[q.size() - 2] + q[q.size() - 1], q.pop_back();
}
Data query(int x, int y)
{
Data ans; ans.clr();
if (q.size()) ans = q[0].Find(x, y);
for (int i = 1; i < (int)q.size(); i++) ans *= q[i].Find(x, y);
return ans;
}
P10540 [THUPC 2024 决赛] 古明地枣的袜子
暴力莫队能卡过(
首先转化下问题,同一个位置的修改其实是很烦的情况,发现将负的放在正的后面就可以让每个修改的位置不同了。
由于询问是询问一个区间,那么更适合将修改变成点,维护信息变成区间信息,那么变成了每个点有一个出现时间,查询区间时间内的后缀和的最大值。
这么转化之后考虑序列分块维护查询,因为如果从后往前扫块间贡献是可以 \(\mathcal{O}(1)\) 计算的。
那么现在对于一个块需要求出后缀和最大值和区间和。
假设现在处在一个块内。
将块内的元素全部提取出来,按照下标排序,那么每个询问一定可以定位到这些元素的一个区间上,而定位出的元素就是这个询问中影响到这个块的全部元素了。
这么说只需要维护 \(O(B^2)\) 个区间的信息就好。
一种暴力的方法。
直接上颗线段树维护,平衡复杂度后是 \(\mathcal{O}(n\sqrt{n\log n})\) 的。
还是挺卡常的,卡不过可以找我。
code
#include <iostream>
#include <algorithm>
#include <vector>
#include <map>
#include <cstring>
#include <cmath>
#include <ctime>
#include <queue>
using namespace std;
const int N = 5e5 + 10, B = 400;
using ll = long long;
const ll inf = 1e18;
class SemTree
{
#define lid id << 1
#define rid id << 1 | 1
#define mid ((cl + cr) >> 1)
public :
ll val[B << 2], tag[B << 2];
inline void Clear()
{
memset(tag, 0, sizeof(tag));
memset(val, 0, sizeof(val));
}
inline void Update(int id, int cl, int cr, int r, int v)
{
if (cr <= r) return val[id] += v, tag[id] += v, void();
if (r > mid) val[lid] += v, tag[lid] += v, Update(rid, mid + 1, cr, r, v);
else Update(lid, cl, mid, r, v);
val[id] = max(val[lid], val[rid]) + tag[id];
}
}T;
int n, m, st[N / B + 10], en[N / B + 10], bel[N];
int qu[B + 10], pre[N], nxt[N], re[N];
ll sum[B + 10], G[B + 10][B + 10], ans[N], ls[N];
struct Node
{
int x, y, id;
}C[N], Q[N], CC[N];
signed main()
{
// freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> C[i].x >> C[i].y, C[i].id = i, CC[i] = C[i];
for (int i = 1; i <= m; i++) cin >> Q[i].x >> Q[i].y, Q[i].id = i, ans[i] = -inf;
sort(C + 1, C + 1 + n, [](Node x, Node y) {return x.x == y.x ? x.y > y.y : x.x < y.x;});
sort(Q + 1, Q + 1 + m, [](Node x, Node y) {return x.x == y.x ? x.y > y.y : x.x < y.x;});
if (C[n].x < n) C[n + 1].x = n, ++n;
for (int i = 1; i <= n; i++) C[i].x = i, CC[C[i].id].x = C[i].x;
for (int i = 1; i <= n; i++) bel[i] = (i - 1) / B + 1, en[bel[i]] = i;
for (int i = 1; i <= m; i++) re[Q[i].id] = i;
int k = bel[n];
for (int i = 1; i <= k; i++) st[i] = en[i - 1] + 1;
for (int i = k; i >= 1; i--)
{
int len = en[i] - st[i] + 1;
for (int j = st[i]; j <= en[i]; j++) qu[j - st[i] + 1] = C[j].id; // O(n)
sort(qu + 1, qu + en[i] - st[i] + 2); // O(n log n)
for (int j = 1; j <= len; j++) sum[j] = sum[j - 1] + CC[qu[j]].y; // O(n)
for (int j = 1; j <= len; j++) // O(B^2 log n)
{
if (j & 1)
{
for (int k = j; k <= len; k++)
T.Update(1, 1, len, CC[qu[k]].x - st[i] + 1, CC[qu[k]].y), G[j][k] = T.val[1];
}
else
{
T.Update(1, 1, len, CC[qu[j - 1]].x - st[i] + 1, -CC[qu[j - 1]].y);
for (int k = len; k >= j; k--)
G[j][k] = T.val[1], T.Update(1, 1, len, CC[qu[k]].x - st[i] + 1, -CC[qu[k]].y);
}
}
if (len & 1) T.Update(1, 1, len, CC[qu[len]].x - st[i] + 1, -CC[qu[len]].y);
for (int j = 1; j <= len; j++)
{
for (int k = qu[j - 1] + 1; k < qu[j]; k++) pre[k] = j - 1, nxt[k] = j;
pre[qu[j]] = nxt[qu[j]] = j;
}
for (int j = 1; j < qu[1]; j++) nxt[j] = 1, pre[j] = 0;
for (int j = qu[len] + 1; j <= n; j++) pre[j] = len, nxt[j] = len + 1;
#pragma unroll(16)
for (int j = 1, nx, pr; j <= m; j++)
{
nx = nxt[Q[j].x], pr = pre[Q[j].y];
ans[j] = max(ans[j], ls[j] + G[nx][pr]);
ls[j] += sum[pr] - sum[nx - 1];
}
}
for (int i = 1; i <= m; i++) cout << ans[re[i]] << '\n';
cerr << '\n' << clock() / 1000.0 << '\n';
return 0;
}
比较牛的方法。
假设以 \((l, r)\) 为一个二维的点,现在从后向前扫,则需要维护的信息构成一个半群,坐标就是其在询问上的时间下标,查询就直接查询单点信息就好。
这样复杂度就是 \(O(n\sqrt{n})\) 的了,常数比不过卡常莫队,唉唉。
用
vector的.swap可比直接赋值快多了!
可能有点抽象
#include <iostream>
#include <algorithm>
#include <vector>
#include <map>
#include <cstring>
#include <cmath>
#include <ctime>
#include <bitset>
#include <queue>
using namespace std;
#define emp emplace_back
const int B = 512;
const int N = 5e5 + 10;
using ll = long long;
const ll inf = 1e18;
struct Data
{
ll a, b;
inline void operator *= (const Data &x)
{
a = max(a, b + x.a);
b += x.b;
}
inline void clr()
{
a = b = 0;
}
inline Data() {a = b = 0;}
inline Data(ll _a, ll _b) {a = _a, b = _b;}
inline friend Data operator * (const Data &x, const Data &y)
{
return {max(x.a, x.b + y.a), x.b + y.b};
}
};
int lr1[N], lr2[N];
bitset <N> orbit;
class State
{
public :
vector<int> r;
vector<Data> num;
int rs;
void operator += (const State &y)
{
int _rs;
_rs = rs + y.rs - 1;
vector<int> lr(_rs);
lr[0] = lr1[0] = lr2[0] = 0;
for (int p1 = 1, p2 = 1, k = 1; p1 < rs || p2 < y.rs;)
{
if (p1 < rs && (p2 >= y.rs || r[p1] <= y.r[p2])) orbit[k] = 0, lr[k] = r[p1], ++p1, ++k;
else orbit[k] = 1, lr[k] = y.r[p2], ++p2, ++k;
}
r.swap(lr);
vector <Data> _num;
_num.resize(_rs * _rs);
for (int i = 0, lr1 = -1, lr2 = 0; i < _rs; i++)
{
if (orbit[i]) ++lr2;
else ++lr1;
for (int j = 0, _lr1 = -1, _lr2 = 0; j < _rs; j++)
{
if (orbit[j]) ++_lr2;
else ++_lr1;
_num[i * _rs + j] = num[lr1 * rs + _lr1] * y.num[lr2 * y.rs + _lr2];
}
}
num.swap(_num), rs = _rs;
}
Data Find(int x, int y)
{
return num[x * rs + y];
}
State(int x, Data d)
{
r.emp(0), r.emp(x);
num.resize(4), num[1] = d;
rs = r.size();
}
State() {rs = 0;}
void Clear() {r.clear(); num.clear(); rs = 0;}
~State() {Clear();}
};
vector <State> q;
void Update(int x, Data d)
{
q.emp(State(x, d));
while (q.size() > 1u && q[q.size() - 1].rs >= q[q.size() - 2].rs)
q[q.size() - 2] += q[q.size() - 1], q.pop_back();
}
void MergeAll()
{
while (q.size() > 1u) q[q.size() - 2] += q[q.size() - 1], q.pop_back();
}
void Clear()
{
q[0].Clear();
q.clear();
}
int n, m, st[N / B + 10], en[N / B + 10], bel[N];
int qu[B + 10], pre[N], nxt[N], re[N], vis[N];
ll sum[B + 10], G[B + 10][B + 10], ans[N], ls[N], S[N];
struct Node
{
int x, id;
ll y;
}C[N], Q[N], CC[N];
signed main()
{
// freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
cin >> C[i].x >> C[i].y, C[i].id = i;
S[i] = S[i - 1];
if (C[i].x == n) S[i] += C[i].y, C[i].y = 0;
CC[i] = C[i];
}
for (int i = 1; i <= m; i++) cin >> Q[i].x >> Q[i].y, Q[i].id = i;
sort(C + 1, C + 1 + n, [](Node x, Node y) {return x.x == y.x ? x.y > y.y : x.x < y.x;});
sort(Q + 1, Q + 1 + m, [](Node x, Node y) {return x.x == y.x ? x.y > y.y : x.x < y.x;});
for (int i = 1; i <= n; i++) C[i].x = i, CC[C[i].id].x = C[i].x;
for (int i = 1; i <= n; i++) bel[i] = (i - 1) / B + 1, en[bel[i]] = i;
for (int i = 1; i <= m; i++) re[Q[i].id] = i;
int k = bel[n];
for (int i = 1; i <= k; i++) st[i] = en[i - 1] + 1;
for (int i = k; i >= 1; i--)
{
int len = en[i] - st[i] + 1;
for (int j = st[i]; j <= en[i]; j++) qu[j - st[i] + 1] = C[j].id; // O(n)
sort(qu + 1, qu + en[i] - st[i] + 2); // O(n log n)
for (int j = 1; j <= len; j++) sum[j] = sum[j - 1] + CC[qu[j]].y; // O(n)
for (int j = 1; j <= len; j++)
{
for (int k = qu[j - 1] + 1; k < qu[j]; k++) pre[k] = j - 1, nxt[k] = j;
pre[qu[j]] = nxt[qu[j]] = j;
}
for (int j = 1; j < qu[1]; j++) nxt[j] = 1, pre[j] = 0;
for (int j = qu[len] + 1; j <= n; j++) pre[j] = len, nxt[j] = len + 1;
for (int j = en[i]; j >= st[i]; j--)
{
Update(pre[C[j].id], Data(max(0ll, C[j].y), C[j].y));
}
MergeAll();
#pragma unroll(16)
for (int j = 1, nx, pr; j <= m; j++)
{
nx = nxt[Q[j].x], pr = pre[Q[j].y];
ans[j] = max(ans[j], ls[j] + q[0].Find(nx - 1, pr).a);
ls[j] += sum[pr] - sum[nx - 1];
}
Clear();
}
for (int i = 1; i <= m; i++) cout << ans[re[i]] + S[Q[re[i]].y] - S[Q[re[i]].x - 1] << '\n';
// cerr << '\n' << clock() / 1000.0 << '\n';
return 0;
}
P5611 [Ynoi2013] D2T2
将值限制看作上面那个题的出现时间,而最大子段和其实也是一个半群信息,所以其实做法十分相似。
然而,查询的区间 \([l, r]\) 使得限制增加了一维,这该怎么办呀。
这时发现一个关键的事情,我们想要的是查询的区间刚好覆盖整块,那么就这么干不就好啦,直接将询问拆成散块和整块,散块跑暴力,整块跑之前提到的方法,由于是半群,可以直接合并,复杂度也保证了。
时间复杂度 \(\mathcal{O}(n\sqrt n)\),常数稍大。
很卡常
#include <iostream>
#include <cstring>
#include <ctime>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 1e5 + 10, B = 250;
using ll = long long;
using pii = pair<int, int>;
#define fi first
#define se second
#define emp emplace_back
struct Data
{
ll lmax, rmax, sum, all;
inline Data(ll _lmax, ll _rmax, ll _sum, ll _all) {lmax = _lmax, rmax = _rmax, sum = _sum, all = _all;}
inline Data() {lmax = rmax = sum = all = 0;}
inline void operator *= (const Data x)
{
sum = max(max(x.sum, sum), rmax + x.lmax);
lmax = max(lmax, all + x.lmax);
rmax = max(x.rmax, x.all + rmax);
all = all + x.all;
}
}pre[N];
int R1[B + 10], R2[B + 10];
struct rmpq
{
vector<int> r;
vector<Data> num;
int rs;
inline rmpq(Data x, int w)
{
r.emp(0), r.emp(w), num.resize(4);
num[0] = num[1] = num[3] = Data(), num[2] = x;
rs = 2;
}
inline void operator += (const rmpq &x)
{
int _rs = 0;
vector<int> _r(rs + x.rs - 1); _r[0] = 0;
R1[0] = R2[0] = 0;
for (int p1 = 1, p2 = 1, k = 1; p1 < rs || p2 < x.rs;)
{
if (p2 >= x.rs || (r[p1] <= x.r[p2] && p1 < rs)) _r[k] = r[p1], ++p1;
else _r[k] = x.r[p2], ++p2;
R1[k] = p1 - 1, R2[k] = p2 - 1, ++k, _rs = k;
}
vector<Data> _num(_rs * _rs);
int *t = R1, *_t = R2;
std::vector<Data>::iterator Q = _num.begin();
for (int i = 0; i < _rs; ++i, ++t, ++_t)
{
int *b = R1, *_b = R2;
for (int j = 0; j < _rs; ++j, ++b, ++_b, ++Q)
{
*Q = num[(*t) * rs + (*b)];
*Q *= x.num[(*_t) * x.rs + (*_b)];
}
}
num.swap(_num);
r.swap(_r);
rs = _rs;
}
~rmpq() {r.clear(); num.clear();}
};
vector<rmpq> pq;
struct Node
{
int L, R, l, r, id;
}q[N];
int n, m, a[N], t[N], wh[N], pr[N], nx[N];
int K, be[N], st[N], en[N], qu[B + 10];
pii _tmp[N];
void Insert(int pos, int w)
{
pq.emp(rmpq(Data(max(0, w), max(0, w), max(0, w), w), pos));
while (pq.size() > 1 && pq[pq.size() - 2].rs <= pq[pq.size() - 1].rs)
pq[pq.size() - 2] += pq[pq.size() - 1], pq.pop_back();
}
void Merge()
{
while (pq.size() > 1) pq[pq.size() - 2] += pq[pq.size() - 1], pq.pop_back();
}
inline Data Query(int x, int y)
{
return pq[0].num[x + y * pq[0].rs];
}
signed main()
{
// freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> a[i], _tmp[i] = {a[i], i};
sort(_tmp + 1, _tmp + 1 + n);
for (int i = 1; i <= n; i++) t[_tmp[i].se] = i, wh[i] = _tmp[i].se;
for (int i = 1; i <= m; i++) cin >> q[i].l >> q[i].r >> q[i].L >> q[i].R, q[i].id = i,
q[i].L = lower_bound(_tmp + 1, _tmp + 1 + n, make_pair(q[i].L, 0)) - _tmp,
q[i].R = upper_bound(_tmp + 1, _tmp + 1 + n, make_pair(q[i].R, n)) - _tmp - 1;
for (int i = 1; i <= n; i++) be[i] = (i - 1) / B + 1, en[be[i]] = i, K = be[i];
for (int i = 1; i <= K; i++) st[i] = en[i - 1] + 1;
for (int i = 1; i <= m; i++)
{
int x = be[q[i].l], y = be[q[i].r], l = q[i].l, r = q[i].r;
if (x != y) r = en[x];
int ql = q[i].L, qr = q[i].R;
for (int j = l, g; j <= r; j++)
if (ql <= t[j] && t[j] <= qr) g = max(0, a[j]), pre[i] *= Data(g, g, g, a[j]);
}
for (int i = 2; i < K; i++)
{
int len = en[i] - st[i] + 1;
for (int j = st[i]; j <= en[i]; j++) qu[j - st[i] + 1] = t[j];
sort(qu + 1, qu + 1 + len);
for (int j = 1; j <= len; j++) pr[qu[j]] = j;
for (int j = st[i]; j <= en[i]; j++) Insert(pr[t[j]], a[j]);
Merge();
int *PR = pr, *NX = nx;
for (int j = 1; j <= len; j++)
{
++PR, ++NX;
for (int p = qu[j - 1] + 1; p < qu[j]; p++, ++PR, ++NX) *PR = j - 1, *NX = j;
*PR = *NX = j;
}
for (int j = qu[len] + 1; j <= n; j++) pr[j] = len, nx[j] = len + 1;
for (int j = 1; j <= m; j++) if (q[j].l < st[i] && en[i] < q[j].r) pre[j] *= Query(nx[q[j].L] - 1, pr[q[j].R]);
pq.clear();
}
for (int i = 1; i <= m; i++)
{
int x = be[q[i].l], y = be[q[i].r], l = q[i].l, r = q[i].r;
if (x != y) l = st[y];
else continue;
int ql = q[i].L, qr = q[i].R;
for (int j = l, g; j <= r; j++)
if (ql <= t[j] && t[j] <= qr) g = max(0, a[j]), pre[i] *= Data(g, g, g, a[j]);
}
for (int i = 1; i <= m; i++) cout << pre[i].sum << '\n';
return 0;
}
P6778 [Ynoi2009] rpdq
人皮电器(rpdq)。
我已神化。
看数据范围就已经知道是莫队了。
然后就是这个这个是可差分的,于是这个问题能够用扫描线去做。
猜的够多了,来试试。
对于两点之间距离,很容易变成求 LCA 的深度,而用扫描线求这个变能够转化为根链加根链查询。
为什么?因为两个点的 LCA 的深度其实可以变成其中一个点根链加 1 个边权,另一个点去查询根链。
那么问题就很简单咯,扫描线根链加,查询根链查,需要一个 \(\mathcal{O}(\sqrt n) \sim \mathcal{O}(1)\) 的数据结构。
很容易想到树分块,这里用的是 Cluster 分块。
维护点到簇顶的贡献和、簇路径的满贡献次数和簇路径点的贡献和。
修改时先修改散块的贡献,直接全部暴力重构,注意更新簇路径点贡献,然后更新满的簇路径贡献。
查询前注意更新一下簇之间的根链前缀和。
另外,这题还需要一个莫队二次离线。
时间复杂度 \(O(n\sqrt n)\),常数爆炸。
压数组了,写的很迷惑
#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 4e5 + 10, B = 500;
#define int uint
using pii = pair<int, int>;
using ll = long long;
#define fi first
#define se second
#define emp emplace_back
struct Node
{
int op, u, d, fa, ls, rs, siz;
int sum;
}tr[N];
int ID[N], ef[N], be[N];
int ts[N], sum[N], sum_b[N], cnt_b[N], val[N], dep[N];
int n, m, rt, cnt;
Node Rake(int x, int y)
{
Node tmp;
tmp.u = tr[x].u, tmp.d = tr[x].d, tmp.siz = tr[x].siz + tr[y].siz;
tmp.sum = tr[x].sum;
tmp.ls = x, tmp.rs = y, tmp.op = 0;
return tmp;
}
Node Compress(int x, int y)
{
Node tmp;
tmp.u = tr[x].u, tmp.d = tr[y].d, tmp.siz = tr[x].siz + tr[y].siz;
tmp.sum = tr[x].sum + tr[y].sum;
tmp.ls = x, tmp.rs = y, tmp.op = 1;
return tmp;
}
void PushUp(int x)
{
int fa = tr[x].fa;
if (tr[x].op) tr[x] = Compress(tr[x].ls, tr[x].rs);
else tr[x] = Rake(tr[x].ls, tr[x].rs);
tr[x].fa = fa, tr[tr[x].ls].fa = x, tr[tr[x].rs].fa = x;
}
int f[N], siz[N], son[N], top[N], QQ[N], _top[N];
vector<pii> G[N];
vector<int> T[N];
Node New(int x, int fa)
{
return (Node){0, fa, x, 0, 0, 0, 1, val[x]};
}
void dfs(int x, int fa)
{
if (x) f[x] = fa, siz[x] = 1;
for (auto [to, w] : G[x])
{
if (to == fa) continue;
val[to] = w, ef[to] = ++cnt, tr[cnt] = New(to, x);
dep[to] = dep[x] + w, dfs(to, x);
if (x) siz[x] += siz[to];
if (siz[to] > siz[son[x]]) son[x] = to;
}
}
int Merge(int x, int y, int op)
{
++cnt, tr[cnt].op = op, tr[cnt].ls = x, tr[cnt].rs = y;
PushUp(cnt);
return cnt;
}
int Calc(vector<int> &p, int l, int r, int op)
{
if (l == r) return p[l];
int mid = l, s = 0;
for (int i = l; i <= r; i++) s += tr[p[i]].siz;
for (int i = l; i < r; i++)
{
mid = i;
if (s < 2 * tr[p[i]].siz) break;
s -= 2 * tr[p[i]].siz;
}
return Merge(Calc(p, l, mid, op), Calc(p, mid + 1, r, op), op);
}
int Build(int x)
{
vector<int> q;
if (x) q.emp(ef[x]);
vector<int> p;
for (x = son[x]; x; x = son[x])
{
p.clear();
p.emp(ef[x]);
for (auto [to, w] : G[f[x]])
{
if (to == x || to == f[f[x]]) continue;
p.emp(Build(to));
}
q.emp(Calc(p, 0, p.size() - 1, 0));
}
return Calc(q, 0, q.size() - 1, 1);
}
void Proc(int x, int op = 0)
{
if (!op)
{
if (tr[x].siz < B) be[tr[x].d] = x, Proc(tr[x].ls, x), Proc(tr[x].rs, x);
else Proc(tr[x].ls), Proc(tr[x].rs);
}
else
{
if (x == 0) return ;
if (!tr[x].ls) be[tr[x].d] = op;
Proc(tr[x].ls, op), Proc(tr[x].rs, op);
}
}
void dfs2(int x)
{
top[x] = tr[be[x]].u, T[be[x]].emp(x);
if (x && !ID[be[x]]) QQ[++QQ[0]] = be[x], ID[be[x]] = 1, siz[QQ[0]] = be[top[x]];
for (auto [to, w] : G[x])
{
if (to == f[x]) continue;
dfs2(to);
}
}
struct MotaoQuery
{
int l, r, id;
}Q[N];
int MTbe[N], GD[N];
int ans[N], _ans[N], w[N], rw[N], s[N], TAG[N];
struct Query
{
int x, y, add, id;
};
vector<Query> q1[N], q2[N];
void dfs3(int x, int fa)
{
if (GD[x]) fa = x;
_top[x] = fa, GD[x] = be[top[x]], son[x] = dep[fa] - dep[top[x]];
for (auto [to, w] : G[x])
{
if (to == f[x]) continue;
dfs3(to, fa);
}
}
void PreWork()
{
for (int i = 1; i <= n; i++) MTbe[i] = (i - 1) / B + 1;
sort(Q + 1, Q + 1 + m, [](MotaoQuery x, MotaoQuery y)
{return (MTbe[x.l] ^ MTbe[y.l]) ? (MTbe[x.l] < MTbe[y.l]) : ((MTbe[x.l] & 1) ? x.r < y.r : x.r > y.r);});
G[0].emp((int)1, (int)0), G[1].emp((int)0, (int)0);
dfs(0, 0);
rt = Build(0);
Proc(rt);
dfs2(0);
for (int i = 1; i <= QQ[0]; i++)
{
int t = tr[QQ[i]].d;
while (t != tr[QQ[i]].u) GD[t] = 1, t = f[t];
}
dfs3(0, 0);
}
void Procs()
{
for (int i = 1; i <= QQ[0]; i++) ts[QQ[i]] = ts[siz[i]] + sum_b[QQ[i]] + cnt_b[QQ[i]] * tr[QQ[i]].sum;
}
int Query(int x)
{
return ts[GD[x]] + sum[x] + cnt_b[be[x]] * son[x];
}
signed main()
{
// freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> m;
for (int i = 1; i < n; i++)
{
int x, y, w; cin >> x >> y >> w;
G[x].emp(y, w), G[y].emp(x, w);
}
for (int i = 1; i <= m; i++) cin >> Q[i].l >> Q[i].r, Q[i].id = i;
PreWork();
for (int i = 1; i <= n; i++) s[i] = s[i - 1] + dep[i];
for (int i = 1, l = 1, r = 0; i <= m; i++)
{
int pl = Q[i].l, pr = Q[i].r;
if (l > pl) q1[r + 1].push_back({pl, l - 1, 0, i}), l = pl;
if (r < pr) q2[l - 1].push_back({r + 1, pr, 0, i}), r = pr;
if (l < pl) q1[r + 1].push_back({l, pl - 1, 1, i}), l = pl;
if (r > pr) q2[l - 1].push_back({pr + 1, r, 1, i}), r = pr;
}
for (int i = 1; i <= n; i++)
{
int fa = top[i], x = i, _fa = _top[i];
while (x != _fa) TAG[x] += val[x], x = f[x];
while (x != fa) TAG[x] += val[x], sum_b[be[x]] += val[x], x = f[x];
for (auto j : T[be[i]]) sum[j] = TAG[j] + (be[f[j]] == be[j] ? sum[f[j]] : 0);
while (fa) cnt_b[be[fa]]++, fa = top[fa];
Procs();
w[i] = Query(i) + w[i - 1];
for (auto j : q2[i])
for (int k = j.x; k <= j.y; k++)
{
if (j.add) _ans[j.id] += Query(k);
else _ans[j.id] -= Query(k);
}
}
memset(sum, 0, sizeof(sum));
memset(sum_b, 0, sizeof(sum_b));
memset(cnt_b, 0, sizeof(cnt_b));
memset(TAG, 0, sizeof(TAG));
for (int i = n; i >= 1; i--)
{
int fa = top[i], x = i, _fa = _top[i];
while (x != _fa) TAG[x] += tr[ef[x]].sum, x = f[x];
while (x != fa) TAG[x] += tr[ef[x]].sum, sum_b[be[x]] += tr[ef[x]].sum, x = f[x];
for (auto j : T[be[i]])
sum[j] = TAG[j] + (be[f[j]] == be[j] ? sum[f[j]] : 0);
while (fa)
{
cnt_b[be[fa]]++;
fa = top[fa];
}
Procs();
rw[i] = Query(i) + rw[i + 1];
for (auto j : q1[i])
for (int k = j.x; k <= j.y; k++)
{
if (j.add) _ans[j.id] += Query(k);
else _ans[j.id] -= Query(k);
}
}
for (int i = 1; i <= m; i++) ans[Q[i].id] += (s[Q[i].r] - s[Q[i].l - 1]) * (Q[i].r - Q[i].l + 2);
for (int i = 1, l = 1, r = 0; i <= m; i++)
{
int pl = Q[i].l, pr = Q[i].r;
if (l > pl) _ans[i] += rw[pl] - rw[l], l = pl;
if (r < pr) _ans[i] += w[pr] - w[r], r = pr;
if (l < pl) _ans[i] -= rw[l] - rw[pl], l = pl;
if (r > pr) _ans[i] -= w[r] - w[pr], r = pr;
_ans[i] += _ans[i - 1];
ans[Q[i].id] -= 2 * _ans[i];
}
for (int i = 1; i <= m; i++) cout << (uint32_t)ans[i] << '\n';
return 0;
}

燃尽了
浙公网安备 33010602011771号