Day 0
线段树复健
P4513 小白逛公园
题意简化:单点修改,查询区间最大子段和。
维护区间和,强制以左端点开始的最大子段和,强制以右端点为结尾的最大子段和,区间最大子段和。
void pushup(int rt) {
t[rt].sum = t[lc].sum + t[rc].sum;
t[rt].mxl = max(t[lc].mxl, t[lc].sum + t[rc].mxl);
t[rt].mxr = max(t[rc].mxr, t[lc].mxr + t[rc].sum);
t[rt].mx = max({t[lc].mx, t[rc].mx, t[lc].mxr + t[rc].mxl , t[rt].mxl , t[rt].mxr});
}
void build(int rt, int l, int r) {
t[rt].l = l, t[rt].r = r;
if (l == r) {
t[rt].sum = t[rt].mx = t[rt].mxl = t[rt].mxr = a[l];
return;
}
int mid = l + r >> 1;
build(lc, l, mid), build(rc, mid + 1, r);
pushup(rt);
}
void change(int rt, int pos, int x) {
if (t[rt].l == t[rt].r) {
t[rt].sum = t[rt].mx = t[rt].mxl = t[rt].mxr = x;
return;
}
if(t[lc].r>=pos)change(lc, pos, x);
else change(rc, pos, x);
pushup(rt);
}
S query(int rt, int l, int r) {
if (l <= t[rt].l and t[rt].r <= r)
return t[rt];
if (r <= t[lc].r)
return query(lc, l, r);
else if (t[rc].l <= l)
return query(rc, l, r);
else {
S lv = query(lc, l, r), rv = query(rc, l, r), ans;
ans.mxl = max(lv.mxl, lv.sum + rv.mxl);
ans.mxr = max(rv.mxr, lv.mxr + rv.sum);
ans.mx = max({lv.mx, rv.mx, lv.mxr + rv.mxl});
ans.sum = lv.sum + rv.sum;
return ans;
}
}
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10;
int n, m, a[N];
struct S {int l, r, sum, mx, mxl, mxr;} t[N << 2];
#define lc rt<<1
#define rc rt<<1|1
void pushup(int rt) {
t[rt].sum = t[lc].sum + t[rc].sum;
t[rt].mxl = max(t[lc].mxl, t[lc].sum + t[rc].mxl);
t[rt].mxr = max(t[rc].mxr, t[lc].mxr + t[rc].sum);
t[rt].mx = max({t[lc].mx, t[rc].mx, t[lc].mxr + t[rc].mxl , t[rt].mxl , t[rt].mxr});
}
void build(int rt, int l, int r) {
t[rt].l = l, t[rt].r = r;
if (l == r) {
t[rt].sum = t[rt].mx = t[rt].mxl = t[rt].mxr = a[l];
return;
}
int mid = l + r >> 1;
build(lc, l, mid), build(rc, mid + 1, r);
pushup(rt);
}
void change(int rt, int pos, int x) {
if (t[rt].l == t[rt].r) {
t[rt].sum = t[rt].mx = t[rt].mxl = t[rt].mxr = x;
return;
}
if(t[lc].r>=pos)change(lc, pos, x);
else change(rc, pos, x);
pushup(rt);
}
S query(int rt, int l, int r) {
if (l <= t[rt].l and t[rt].r <= r)
return t[rt];
if (r <= t[lc].r)
return query(lc, l, r);
else if (t[rc].l <= l)
return query(rc, l, r);
else {
S lv = query(lc, l, r), rv = query(rc, l, r), ans;
ans.mxl = max(lv.mxl, lv.sum + rv.mxl);
ans.mxr = max(rv.mxr, lv.mxr + rv.sum);
ans.mx = max({lv.mx, rv.mx, lv.mxr + rv.mxl});
ans.sum = lv.sum + rv.sum;
return ans;
}
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; ++i)
cin >> a[i];
build(1, 1, n);
while (m--) {
int k;
cin >> k;
if (k == 1) {
int l, r;
cin >> l >> r;
if (l > r) swap(l, r);
cout << query(1, l, r).mx << '\n';
}
else {
int p, s;
cin >> p >> s;
change(1, p, s);
}
}
return 0;
}
P2073 送花
传送门
题意简化:每个节点有\(W,C\)两属性,
1 W C
,插入一个节点,若已有节点权值为 C,忽略;
2
,删除 C 最大的节点,若无节点,忽略;
3
,删除 C 最小的节点,若无节点,忽略;
-1
,求出当前的\(\Sigma W, \Sigma C\)
用平衡树维护即可,较板。
#include <iostream>
#include <climits>
using namespace std;
const int N = 3e5 + 10;
int val[N], ch[N][2], siz[N], fa[N], W[N];
int cnt, n, rt, sumw, sumc;
void pushup(int x) {siz[x] = siz[ch[x][0]] + siz[ch[x][1]] + 1;}
int getch(int x) {return x == ch[fa[x]][1];}
void rotate(int x) {
int y = fa[x], z = fa[y], k = getch(x);
ch[y][k] = ch[x][k ^ 1];
if (ch[x][k ^ 1])
fa[ch[x][k ^ 1]] = y;
ch[x][k ^ 1] = y; fa[y] = x;
fa[x] = z;
if (z)
ch[z][ch[z][1] == y] = x;
pushup(y);
pushup(x);
}
void splay(int x, int goal = 0) {
for (int f = fa[x]; (f = fa[x]) != goal; rotate(x))
if (fa[f] != goal)
rotate(getch(x) == getch(f) ? f : x);
if (goal == 0)
rt = x;
}
void ins(int w, int c) {
int f = 0, x = rt;
while (x && val[x] != c) {f = x; x = ch[x][c > val[x]];}
if (x) return;
else {
x = ++cnt;
if (f)
ch[f][c > val[f]] = x;
val[x] = c, W[x] = w;
sumw += w, sumc += c;
fa[x] = f;
siz[x] = 1;
}
splay(x);
}
void find(int k) {
int x = rt;
while (ch[x][k > val[x]] && val[x] != k)
x = ch[x][k > val[x]];
splay(x);
}
int getpre(int k) {
find(k);
if (val[rt] < k)
return rt;
int x = ch[rt][0];
while (ch[x][1])
x = ch[x][1];
splay(x);
return x;
}
int getnxt(int k) {
find(k);
if (val[rt] > k)
return rt;
int x = ch[rt][1];
while (ch[x][0])
x = ch[x][0];
splay(x);
return x;
}
void del(int k) {
int pre = getpre(k), nxt = getnxt(k);
splay(pre), splay(nxt, pre);
int x = ch[nxt][0];
sumc -= val[x], sumw -= W[x];
ch[nxt][0] = 0;
pushup(nxt);
pushup(rt);
}
signed main() {
ins(0, INT_MAX), ins(0, INT_MIN);
sumw = sumc = 0; int op;
while (true) {
cin >> op;
if (op == -1) {
cout << sumw << ' ' << sumc;
break;
}
if (op == 1) {
int w, c;
cin >> w >> c;
ins(w, c);
}
if (op == 2) {
int tmp = getpre(INT_MAX);
if (val[tmp] != INT_MIN)
del(val[tmp]);
}
if (op == 3) {
int tmp = getnxt(INT_MIN);
if (val[tmp] != INT_MAX)
del(val[tmp]);
}
}
return 0;
}
P3580 书架
传送门
难度虚高,实际上用 std::vector
就能水过。
#include <iostream>
#include <vector>
#include <string>
using namespace std;
const int N = 2e5;
vector<int> v;
string book[N];
int n, m, cnt = -1, q, pos;
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; ++i) {
string s; cin >> s;
book[++cnt] = s;
v.push_back(cnt);
}
cin >> m;
for (int i = 1; i <= m; ++i) {
string s;
cin >> s >> pos;
book[++cnt] = s;
auto it = v.begin() + pos;
v.insert(it, cnt);
}
cin >> q;
while (q--) {
cin >> pos;
cout << book[v[pos]] << '\n';
}
return 0;
}
注意到
std::vector
里面最好不要套STL
,不然复杂度爆炸。
P4588 [TJOI2018] 数学计算
题意简化:给出一个数 \(x\):
1 y
,\(x \leftarrow x\times y\)
2 pos
,\(x\leftarrow {x \over \text{第 pos 次操作所乘的数}}\)
所有操作对\(MOD\)取模,每次操作后输出结果。
线段树,思维难度较高。
显然,要把每次操作的数存下来,相当于一个序列。
要支持什么?
想想······
更改一个数,查询总乘积?
抽象一点······
点更新,段查询!!!
然后 1
就变成在当前询问的位置加上\(x\),2
就变为将\(pos\)位改为\(1\)。
查询只需输出tree[0].val
#include <iostream>
using namespace std;
#define int long long
const int N = 1e5 + 10;
struct S {int l, r, val;}t[N << 2];
int T, q, MOD, cnt;
#define lc rt<<1
#define rc rt<<1|1
void pushup(int rt) {t[rt].val = t[lc].val * t[rc].val % MOD;}
void build(int rt, int l, int r) {
t[rt].l = l, t[rt].r = r;
if (l == r) {
t[rt].val = 1;
return;
}
int mid = l + r >> 1;
build(lc, l, mid), build(rc, mid + 1, r);
pushup(rt);
}
void change(int rt, int pos, int val) {
if (t[rt].l == t[rt].r) {
t[rt].val = val % MOD;
return;
}
int mid = t[rt].l + t[rt].r >> 1;
if (pos <= mid)
change(lc, pos, val);
else if (pos > mid)
change(rc, pos, val);
pushup(rt);
}
signed main() {
cin >> T;
while (T--) {
cnt = 0;
cin >> q >> MOD;
build(1, 1, q);
for (int i = 1, op, x; i <= q; ++i) {
cin >> op >> x;
if (op == 1) {
change(1, i, x);
cout << t[1].val % MOD << '\n';
}
else {
change(1, x, 1);
cout << t[1].val % MOD << '\n';
}
}
}
return 0;
}
Day 1
线段树复健
P2572 [SCOI2010] 序列操作
传送门
太恶心了,先咕着,直接看代码。
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
struct S {int n0, n1, l0, l1, r0, r1, mx0, mx1;}t[N << 2];
//The num of 0/1, From left max 1/0, Frome right 1/0, max 1/0;
int a[N], Ctag[N << 2], Itag[N << 2], len[N << 2];
int n, m;
#define lc rt<<1
#define rc rt<<1|1
S merge(S x, S y) {
return (S) {
x.n0 + y.n0,
x.n1 + y.n1,
x.n1 ? x.l0 : x.n0 + y.l0,
x.n0 ? x.l1 : x.n1 + y.l1,
y.n1 ? y.r0 : y.n0 + x.r0,
y.n0 ? y.r1 : y.n1 + x.r1,
max({x.mx0, y.mx0, x.r0 + y.l0}),
max({x.mx1, y.mx1, x.r1 + y.l1})
};
}
void pushup(int rt) {
t[rt] = merge(t[lc], t[rc]);
}
void upd(int rt, int type) {//0/1: cover as 0/1; 2: inv
if (type == 0) {
Ctag[rt] = 0, Itag[rt] = 0;
t[rt] = (S) {len[rt], 0, len[rt], 0, len[rt], 0, len[rt], 0};
return;
}
else if (type == 1) {
Ctag[rt] = 1, Itag[rt] = 0;
t[rt] = (S) {0, len[rt], 0, len[rt], 0, len[rt], 0, len[rt]};
}
else if (type == 2) {
Itag[rt] ^= 1; S v = t[rt];
t[rt] = (S) {v.n1, v.n0, v.l1, v.l0, v.r1, v.r0, v.mx1, v.mx0};
}
}
void pushdown(int rt) {
if (Ctag[rt] != -1) {
upd(lc, Ctag[rt]); upd(rc, Ctag[rt]);
}
if (Itag[rt]) {
upd(lc, 2); upd(rc, 2);
}
Ctag[rt] = -1, Itag[rt] = 0;
}
void build(int rt, int l, int r) {
len[rt] = r - l + 1, Ctag[rt] = -1;
if (l == r) {
int v = a[l];
t[rt] = (S) {v^1, v, v^1, v, v^1, v, v^1, v};
return;
}
int mid = l + r >> 1;
build(lc, l, mid), build(rc, mid + 1, r);
pushup(rt);
}
void change(int rt, int l, int r, int nl, int nr, int type) {
if (l <= nl && nr <= r) {
upd(rt, type);
return;
}
if (r < nl || nr < l)
return;
pushdown(rt);
int mid = nl + nr >> 1;
change(lc, l, r, nl, mid, type);
change(rc, l, r, mid + 1, nr, type);
pushup(rt);
}
S query(int rt, int l, int r, int nl, int nr) {
if (l <= nl && nr <= r) {
return t[rt];
}
pushdown(rt);
int mid = nl + nr >> 1;
if (r <= mid) return query(lc, l, r, nl, mid);
if (l > mid) return query(rc, l, r, mid + 1, nr);
return merge(query(lc, l, r, nl, mid), query(rc, l, r, mid + 1, nr));
}
signed main() {
cin >> n >> m;
for (int i = 1; i <= n; ++i)
cin >> a[i];
build(1, 1, n);
while (m--) {
int op, l, r;
cin >> op >> l >> r;
++l, ++r;
if (op <= 2) {
change(1, l, r, 1, n, op);
}
else if (op == 3) {
cout << query(1, l, r, 1, n).n1 << endl;
}
else {
cout << query(1, l, r, 1, n).mx1 << endl;
}
}
return 0;
}
栈
一眼题
B3614、B4387
单调栈
P5788
队列
一眼题
B3616、P3887
单调队列
P1886
P1714 切蛋糕
易想到用前缀和维护\(\text{sum}(l, r)\),然后问题就变成了:
枚举\(r\),尝试用\(pre_r - \min\{pre_l, pre_{r - 1}\}\)更新\(ans\),其中 \(l = \min(0, r - m)\)
然后用单调队列维护定长区间最值即可(但是我用的是ST表)
#include <iostream>
using namespace std;
const int N = 5e6 + 10, logMAX = 21;
int n, m, ans = -0x7fffffff, a[N], st[N][logMAX], log[N];
void init() {
log[1] = 0;
cin >> n >> m;
for (int i = 2; i <= n; ++i) log[i] = log[i >> 1] + 1;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
a[i] += a[i - 1];
st[i][0] = a[i];
}
for (int j = 1; j <= logMAX; ++j)
for (int i = 1; i + (1 << j) - 1 <= n; ++i)
st[i][j] = min(st[i][j - 1], st[i + (1 << j - 1)][j - 1]);
}
int get(int l, int r) {
int s = log[r - l + 1];
return min(st[l][s], st[r - (1 << s) + 1][s]);
}
int main() {
init();
for (int i = 1; i <= n; ++i) {
int l = (i - m < 0 ? 0 : i - m);
ans = max(ans, a[i] - get(l, i - 1));
}
cout << ans;
return 0;
}
P2085 最小函数值
易发现所有函数在\(x\in\N_+\)范围内单增,故\(x=1\)时函数有最小值,将这些值放入小根堆内,每次输出堆头,并将堆顶函数在\(x+1\)处的值放入堆内,直到输出\(m\)个。
#include <iostream>
#include <queue>
using namespace std;
const int N = 1e4 + 10;
#define mp make_pair
struct S {int x, val, id;};
bool operator > (S a, S b) {return a.val > b.val;};
priority_queue<S, vector<S>, greater<S> > q;
int a[N], b[N], c[N], cnt = 0;
S calc(S s) {return (S){s.x, s.x * s.x * a[s.id] + s.x * b[s.id] + c[s.id], s.id};}
int main() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
cin >> a[i] >> b[i] >> c[i];
q.push(calc((S) {1, 0, i}));
}
while (cnt < m) {
S t = q.top(); q.pop(); cout << t.val << ' ';
++t.x;
q.push(calc(t));
cnt++;
}
return 0;
return 0;
}
并查集
P1525 [NOIP2010 提高组] 关押罪犯
咕咕咕
P1196 [NOI2002] 银河英雄传说
咕咕咕
Day2
教练的分块课件.md
前言:
首先,我们来考虑这样一个模型:有一段连续的序列 a[1...n]
,然后现在我们需要执行几类操作:
出题人: 求出其中一段区间的和
你:前缀和
出题人:区间加上某个值
你:线段树
出题人:查询一段区间上有多少个数<k (k>0且给定)
你:5555
出题人:对了,忘了告诉你了,k每次都不一样,还有,极其的多。另外,为了防止装B不让写平衡树(另一种能干很多事情的优秀数据结构)+线段树,占用空间不能超过xxxxMb
你:X_X
原理:
下面就分享一个暴力算法:分块
分块,顾名思义,就是把一段序列分成一小块一小块来处理,维护。
我们把一段当成一个整体,只记录维护整体的有关信息,就是分块。
首先,对于前言说得那道题,很朴素的做法就是:
-
从询问区间的\(l\)到\(r\)扫过去,每回加上扫到的值;
-
直接把
a[i]
重新赋值不就得了:a[i]=new(a[i]);
-
从询问区间的\(l\)到\(r\)扫过去,每回遇到\(< k\)的位置,答案\(+1\);
没错,这种做法很傻是不是?
但是,分块就是在这个基础上暴力优化的!!!
假设我们总共的序列长度为\(n\),然后我们把它切成\(\sqrt n\)块,每个块里面有\(\sqrt n\)个数(为什么是根号呢?后面会解释 or 听我口胡)
然后把每一块里的东西当成一个整体来看,
现在解释几个本文用到的术语:
完整块:被 操作区间 完全覆盖的块
不完整块:操作区间 不完全覆盖的块
然后我们先看看怎么得出答案:
-
对于完整的块,我们希望有个东西能直接找出这整个块的和,于是 每个块要** 维护这个块的所有元素的和**
- 对于不完整块,因为元素比较少(最多有 \(\sqrt n\)),对比一下,我们可以直接暴力扫这个小块统计答案
-
这里,我们换种思路,记录一个lazy标记,表示整个块被加上过多少了:
-
对于完整块,我们直接
lazy+=
加上的数x
,块内的和ans += x * 元素个数
(因为每个元素都被加上了x
) -
对于不完整块,直接暴力修改就好了,顺便可以把
lazy
标记清了。
-
-
哎呀,这个有点难度啊:
-
要在每个完整块内寻找小于一个值的元素数,显然我们不得不要求块内元素是有序的,这样就能用二分,对块内查询。
-
不完整的块暴力就好
-
这样的话需要提前对每块里面的元素做一遍排序就好.
-
但是当有修改的话,因为整个块同时加上(减去)一个数,每个数的相对大小是不会变的,但是如果是不完全块就会改变,这样的话,还是因为元素个数小,重新新排一下不就得了?
-
然后,这道题就用了一种看似高大上的方法做完了……比之前傻傻的暴力是不是好看很多呢
咱们先从小的开始说……
分块入门
例题1
口胡即可
例题2:
代码?没有! 懒得写
例题3:
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
int n , len , a[50010] , pos[50010] , tag[50010] , b[50010];
void chenge(int l,int r,int c)
{
if(pos[l] == pos[r])
{
for(int i = l;i <= r;i ++) a[i] += c;
for(int i = (pos[l] - 1) * len + 1;i <= min(pos[l] * len,n);i ++) b[i] = a[i];
sort(b+(pos[l] - 1) * len + 1,b+1+min(n,pos[l] * len));
return ;
}
for(int i = l;i <= pos[l] * len;i ++) a[i] += c;
for(int i = (pos[l] - 1) * len + 1;i <= pos[l] * len;i ++) b[i] = a[i];
sort(b+(pos[l] - 1) * len + 1,b+1+pos[l] * len);
for(int i = pos[l] + 1;i <= pos[r] - 1;i ++) tag[i] += c;
for(int i = (pos[r] - 1) * len + 1;i <= r;i ++) a[i] += c;
for(int i = (pos[r] - 1) * len + 1;i <= min(pos[r] * len,n);i ++) b[i] = a[i];
sort(b+(pos[r] - 1) * len + 1,b+1+min(pos[r] * len,n));
}
int query(int l,int r,int c)
{
int res = 0;
if(pos[l] == pos[r])
{
for(int i = l;i <= r;i ++) if(a[i] + tag[pos[i]] < c * c) res ++;
return res;
}
for(int i = l;i <= pos[l] * len;i ++) if(a[i] + tag[pos[i]] < c * c) res ++;
for(int i = pos[l] + 1;i <= pos[r] - 1;i ++)
{
res += lower_bound(b+(i-1)*len+1,b+1+i*len,c * c - tag[i]) - b - 1 - (i - 1) * len;
}
for(int i = (pos[r] - 1) * len + 1;i <= min(n,r);i ++) if(a[i] + tag[pos[i]] < c * c) res ++;
return res;
}
int main()
{
scanf("%d",&n);
len = sqrt(n);
for(int i = 1;i <= n;i ++)
{
pos[i] = (i - 1) / len + 1;
scanf("%d",&a[i]);
b[i] = a[i];
}
for(int i = 1;i <= pos[n];i ++) sort(b + (i - 1) * len + 1,b + 1 + min(n,i * len));
for(int i = 1 , opt , l , r , c;i <= n;i ++)
{
scanf("%d%d%d%d",&opt,&l,&r,&c);
if(opt == 0) chenge(l,r,c);
else printf("%d\n",query(l,r,c));
}
return 0;
}
再讲点吧。
没讲的就留作练习题。
上点难度……
例题4
例题5
(令我绝望的)蒲公英
例题6
且慢,请我口胡
总结
以上差不多就是所有的分块内容了 , 分块的应用还有许多 遇事不决先分块 , 希望大家可以发现更多。