题解:P10637 BZOJ4262 Sum
THUPOST_REMAKE · · 题解
本题解介绍的是不依赖序列随机特性的“序列扫描线+维护线段树历史和”的做法。时间复杂度为
本文将具体介绍本题的具体思路,从矩阵乘法视角理解线段树历史和,线段树半群结构优化,以及其他类似习题的简单介绍。面向初学者会写的相对详尽一些。
本题题意
给定一个长度为
题目思路
首先题目中的
这个东西在线好像还是不太好求,我们可以考虑采用离线扫描线的思路来完成。将扫描线的过程分为两个维度,以按照
显然,原始要求的
接下来考虑扫描线的过程中,序列维度如何变化。考虑时间版本
但是维护区间赋值这个操作并不适合用线段树历史和来维护。那么我们可以考虑结合单调栈来维护
然后将所有查询挂在时间维的
从矩阵视角理解线段树历史和
本题当中的线段树历史和需要完成以下操作:
维护序列
按照通常方式去思考需要一定的思维量,但是如果我们使用矩阵乘法的方式去理解就会轻松很多。
设对于线段树上的每个节点,我们要维护的是列向量
对于区间加法操作,有:
对于刷新历史和操作,有:
此时的单位矩阵为
半群结构的优化
虽然对于本题来说,维护
那么我们就需要观察上述矩阵乘法当中,有哪些位置的元素是会发生变化的,那么我们的半群只需要包含这些元素即可。不难发现对于本题的矩阵
接下来为该半群设计一个等价于矩阵乘法的运算,其实就是把这三个元素放回原本的矩阵当中,观察他们的变化是什么样的。
所以设两个半群
向量本身不需要优化,基于矩阵的优化半群和向量的运算类似上述处理即可,留给读者自己推导。本文给出的代码也是优化半群之后的版本。为了版面,完整代码放在最后。
此外,维护区间历史最值(见历史最值模板题)也可以先利用
综上,我们以时间复杂度
拓展练习 1 : [NOIP2022] 比赛
开头就说了,本题是该拓展练习的绝佳前置练习。
给定长度
基本就是把这题的一个序列扩展成两个序列。对于每个时间版本
而由于单次矩阵乘法运算量
拓展练习 2 : SPOJ GSS2
区间最大子段和,但是子段内相同的数只会贡献一次(比如选取子段是
可以类似HH 的项链一题,记录每个位置
本题代码
const int N = 100010;
int n, q;
int a[N];
i64 ans[N];
namespace gen
{
const int mod = 1000000000;
i64 fst = 1023, sec = 1025;
void init()
{
for (int i = 1; i <= n; ++i)
a[i] = fst ^ sec, fst = fst * 1023 % mod, sec = sec * 1025 % mod;
}
}
struct query
{
int l1, r1, r, id, flag; // flag = 1 : add -1 : sub
bool operator<(const query &o) const { return r < o.r; }
} qu[N];
// monotone stack
struct interval
{
int l, r, mx;
} stk[N];
int stksz;
// history sum seg-tree
inline int lc(int u) { return u << 1; }
inline int rc(int u) { return (u << 1) | 1; }
struct vec
{
i64 a, len, his;
vec(i64 _a = 0, i64 l = 0, i64 h = 0) : a(_a), len(l), his(h) {}
vec operator+(const vec &o) const { return vec(a + o.a, len + o.len, his + o.his); }
};
struct mat
{
i64 a, b, c;
mat() { a = b = c = 0; }
mat(i64 _a, i64 _b, i64 _c) : a(_a), b(_b), c(_c) {}
mat(i64 v, bool flag) { (flag ? (b = 1, a = 0) : (a = v, b = 0)), c = 0; } // flag 1 : his = his + a 0 : a += v
mat operator*(const mat &o) const { return mat(a + o.a, b + o.b, o.a * b + c + o.c); }
vec operator*(const vec &o) const { return vec(o.a + a * o.len, o.len, b * o.a + c * o.len + o.his); }
};
struct node
{
mat tag;
vec info;
void init(int val) { info = vec(val, 1, val); }
void modify(const mat &v) { tag = v * tag, info = v * info; }
} tr[N << 2];
void pushup(int u) { tr[u].info = tr[lc(u)].info + tr[rc(u)].info; }
void pushdown(int u) { tr[lc(u)].modify(tr[u].tag), tr[rc(u)].modify(tr[u].tag), tr[u].tag = mat(); }
void build(int u, int l, int r)
{
tr[u].tag = mat();
if (l == r)
return tr[u].init(0);
int m = (l + r) >> 1;
build(lc(u), l, m), build(rc(u), m + 1, r), pushup(u);
}
void modify(int u, int L, int R, int l, int r, const mat &v)
{
if (L > r || R < l)
return;
if (l <= L && R <= r)
return tr[u].modify(v);
pushdown(u);
int M = (L + R) >> 1;
modify(lc(u), L, M, l, r, v), modify(rc(u), M + 1, R, l, r, v);
pushup(u);
}
vec query_vec(int u, int L, int R, int l, int r)
{
if (L > r || R < l)
return vec();
if (l <= L && R <= r)
return tr[u].info;
pushdown(u);
int M = (L + R) >> 1;
return query_vec(lc(u), L, M, l, r) + query_vec(rc(u), M + 1, R, l, r);
}
void clr() { build(1, 1, n), stksz = 0; }
void solve()
{
int j = 1;
while (!qu[j].r)
++j;
for (int i = 1; i <= n; ++i)
{
// modify version r
modify(1, 1, n, i, i, mat(a[i], 0));
while (stksz && stk[stksz].mx < a[i])
modify(1, 1, n, stk[stksz].l, stk[stksz].r, mat(a[i] - stk[stksz].mx, 0)), --stksz;
++stksz, stk[stksz] = {stk[stksz - 1].r + 1, i, a[i]};
modify(1, 1, n, 1, n, mat(0, 1)); // add version r's sum to history.
for (; j <= q && qu[j].r == i; ++j)
ans[qu[j].id] += qu[j].flag * query_vec(1, 1, n, qu[j].l1, qu[j].r1).his;
}
}
int main()
{
n = 100000, gen::init();
q = rd();
for (int i = 1; i <= q; ++i)
{
int l1 = rd(), r1 = rd(), l2 = rd(), r2 = rd();
qu[(i << 1) - 1] = {l1, r1, l2 - 1, i, -1}, qu[i << 1] = {l1, r1, r2, i, 1};
}
q <<= 1, std::sort(qu + 1, qu + q + 1);
clr(), solve();
for (int i = 1; i <= n; ++i)
a[i] = -a[i];
clr(), solve();
q >>= 1;
for (int i = 1; i <= q; ++i)
wr(ans[i]), putchar('\n');
}