一些有意思的线段树
Suzt_ilymtics · · 个人记录
更好的阅读体验?
写在前面
想必大家都有了一定的线段树基础,所以来做点更有意思的线段树吧(
转存的 Limit の线段树题单
CF803G Periodic RMQ Problem
先来道黑题热热身
Description
题面:CF803G Periodic RMQ Problem
给你一个序列
a 让你支持: 区间赋值;询问区间最小值 我们觉得这个问题太水了,所以我们不会给你序列a 而是给你序列一个长度为n 的序列b ,把b 复制粘贴k 次就可以得到a 。 数据范围:n \le 10^5,k \le 10^4,q \le 10^5,b_i \le 10^9, 1 \le l \le r \le n \times k
Solution
维护的线段树操作不难,麻烦的地方在于对原序列的处理,理解思想后完全可以自己yy出代码。
看到 “区间覆盖,区间最小值” ,这不板子?
但是,序列长度可是
发现询问涉及到的点只有
注意:对答案有贡献的信息不仅仅是询问涉及到的点,还有相邻的两个涉及到的点的区间也对答案有贡献 (因为这个区间内的最小值可能比两个端点更小)。
蓝色箭头是询问涉及过的点,相邻的两个涉及过的点的区间被压缩成一个点来处理(如果相邻两个涉及过的点之间没有区间就不压缩)。
把这两种点都放进一个新的序列(红色序列),然后在新的序列上进行修改查询操作即可。 注意映射好询问涉及到的点在红色序列中的位置。
图床崩了,原图丢失,有时间在画一个
注意将区间压缩成一个点时的处理:
- 如果区间长度超过
n ,直接加入原来整段区间的最小值(但是数据没卡这个地方) - 如果在同一个区间内,加入这个区间内的最小值
- 如果跨越了两个区间,加入区间
[1, r] 和[l, n] 的最小值
这种做法在复杂度和空间消耗上都比较优秀。
剩下的看代码吧,重要步骤都有注释,如果有不懂的也可以在评论区提出
Code
/*
Work by: Suzt_ilymics
Knowledge: ??
Time: O(??)
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define LL long long
#define orz cout<<"lkp AK IOI!"<<endl
using namespace std;
const int MAXN = 2e5+5; // 注意开的数组大小(自己算算应该用多少
const int INF = 1e9+7;
const int mod = 1e9+7;
struct Ques{
int opt, l, r, val;
}q[MAXN];
int n, K, m;
int a[MAXN], b[MAXN << 1], Cnt = 0, pre[MAXN << 1];
int date[MAXN], cnt = 0, date_num = 0;
int read(){
int s = 0, f = 0;
char ch = getchar();
while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
return f ? -s : s;
}
struct Big_Seg{ // 采用结构体,码量会更少哦
#define lson i << 1
#define rson i << 1 | 1
struct Tree{
int min, lazy;
}tree[MAXN << 3];
void Push_up(int i) { tree[i].min = min(tree[lson].min, tree[rson].min); }
void Push_down(int i) {
if(tree[i].lazy) {
tree[lson].lazy = tree[rson].lazy = tree[i].lazy;
tree[lson].min = tree[rson].min = tree[i].lazy;
tree[i].lazy = 0;
}
}
void Build(int i, int l, int r) { //两个建树分别给两个线段树用
if(l == r) { tree[i].min = b[l]; return ; }
int mid = (l + r) >> 1;
Build(lson, l, mid), Build(rson, mid + 1, r);
Push_up(i);
}
void Build0(int i, int l, int r) {
if(l == r) { tree[i].min = a[l]; return ; }
int mid = (l + r) >> 1;
Build0(lson, l, mid), Build0(rson, mid + 1, r);
Push_up(i);
}
void Change(int i, int l, int r, int L, int R, int k) {
if(L <= l && r <= R) {
tree[i].min = tree[i].lazy = k;
return ;
}
Push_down(i);
int mid = (l + r) >> 1;
if(mid >= L) Change(lson, l, mid, L, R, k);
if(mid < R) Change(rson, mid + 1, r, L, R, k);
Push_up(i);
}
int Get_min(int i, int l, int r, int L, int R) {
if(L <= l && r <= R) return tree[i].min;
Push_down(i);
int mid = (l + r) >> 1, ans = INF;
if(mid >= L) ans = min(ans, Get_min(lson, l, mid, L, R));
if(mid < R) ans = min(ans, Get_min(rson, mid + 1, r, L, R));
return ans;
}
}Seg[2];
int main()
{
n = read(), K = read();
for(int i = 1; i <= n; ++i) a[i] = read();
Seg[0].Build0(1, 1, n); // 先对给定的小序列建树
m = read();
for(int i = 1; i <= m; ++i) {
q[i].opt = read(), q[i].l = read(), q[i].r = read();
date[++ cnt] = q[i].l, date[++ cnt] = q[i].r;
if(q[i].opt == 1) q[i].val = read();
}
sort(date + 1, date + cnt + 1); date[0] = -INF;
for(int i = 1; i <= cnt; ++i) if(date[i] != date[i - 1]) date[++ date_num] = date[i]; // 离散化
//一下是构造新序列b过程(即图中的红色序列
for(int i = 1; i < date_num; ++i) {
if(date[i] % n == 0) b[++Cnt] = a[n];
else b[++Cnt] = a[date[i] % n];
pre[i] = Cnt; // 进行第二次映射的处理
if(date[i + 1] > date[i] + 1){
if((date[i + 1] - 1) - (date[i] + 1) >= n) { // 压缩区间长度超过 n 时
b[++Cnt] = Seg[0].tree[1].min;
continue;
}
int l = (date[i] + 1) % n, r = (date[i + 1] - 1) % n; //映射到复制前的序列中的位置
if(l == 0) l = n;
if(r == 0) r = n;
if(l <= r) b[++Cnt] = Seg[0].Get_min(1, 1, n, l, r); // 在一个区间内
else b[++Cnt] = min(Seg[0].Get_min(1, 1, n, 1, r), Seg[0].Get_min(1, 1, n, l, n)); // 不在一个区间内,用两段区间合并
}
}
if(date[date_num] % n == 0) b[++Cnt] = a[n];
else b[++Cnt] = a[date[date_num] % n];
pre[date_num] = Cnt;
for(int i = 1; i <= m; ++i) {
q[i].l = lower_bound(date + 1, date + date_num + 1, q[i].l) - date; // 询问的点向离散化后映射
q[i].r = lower_bound(date + 1, date + date_num + 1, q[i].r) - date;
q[i].l = pre[q[i].l], q[i].r = pre[q[i].r]; // 向b序列中的映射
}
Seg[1].Build(1, 1, Cnt); //对 b序列建树
for(int i = 1; i <= m; ++i) { //直接修改+查询即可
if(q[i].opt == 1) Seg[1].Change(1, 1, Cnt, q[i].l, q[i].r, q[i].val);
else printf("%d\n", Seg[1].Get_min(1, 1, Cnt, q[i].l, q[i].r));
}
return 0;
}
总结
对于所维护的区间过大时,考虑离散化只留下有用的信息来达到节约空间的目的。
CF915E Physical Education Lessons
Description
题面:CF915E Physical Education Lessons
一个长度为
1e9 的01 序列,开始都是1,要求支持区间修改0/1 ,每次修改后都要输出整个序列中1 的个数
Solution
那么最多只会模
证明:
-
如果
m > x , 那么x \mod m = x -
如果
m < x ,考虑如何让x 剩下的值最大?一个显然的想法是模数m 尽可能大 如果m > \frac{x}{2} ,那么剩下的一定小于\frac{x}{2} ,如果m < \frac{x}{2} ,那么更不必说了,所以当m = \frac{x}{2} 时,x 剩下的值最大,所以x 最多进行取模\log x 次
代码的话,应该和区间开方差不多吧
Code
/*
Work by: Suzt_ilymics
Knowledge: ??
Time: O(??)
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define LL long long
#define int long long
#define orz cout<<"lkp AK IOI!"<<endl
using namespace std;
const int MAXN = 1e5+5;
const int INF = 1e9+7;
const int mod = 1e9+7;
int n, m;
int a[MAXN];
int read(){
int s = 0, f = 0;
char ch = getchar();
while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
return f ? -s : s;
}
namespace Seg{
#define lson i << 1
#define rson i << 1 | 1
struct Tree{
int sum, max;
}tree[MAXN << 2];
void Push_up(int i) {
tree[i].sum = tree[lson].sum + tree[rson].sum;
tree[i].max = max(tree[lson].max, tree[rson].max);
}
void Build(int i, int l, int r) {
if(l == r) {
tree[i].max = tree[i].sum = a[l];
return ;
}
int mid = (l + r) >> 1;
Build(lson, l, mid), Build(rson, mid + 1, r);
Push_up(i);
}
void Change(int i, int l, int r, int L, int R, int k) {
if(L <= l && r <= R) {
tree[i].max = tree[i].sum = k;
return ;
}
int mid = (l + r) >> 1;
if(mid >= L) Change(lson, l, mid, L, R, k);
else Change(rson, mid + 1, r, L, R, k);
Push_up(i);
}
void Sec_Mod(int i, int l, int r, int L, int R, int k) {
if(tree[i].max < k) return ;
if(l == r) {
tree[i].sum = tree[i].max = tree[i].sum % k;
return ;
}
int mid = (l + r) >> 1;
if(mid >= L) Sec_Mod(lson, l, mid, L, R, k);
if(mid < R) Sec_Mod(rson, mid + 1, r, L, R, k);
Push_up(i);
}
int Get_Sum(int i, int l, int r, int L, int R) {
if(L <= l && r <= R) return tree[i].sum;
int mid = (l + r) >> 1, ans = 0;
if(mid >= L) ans += Get_Sum(lson, l, mid, L, R);
if(mid < R) ans += Get_Sum(rson, mid + 1, r, L, R);
return ans;
}
}
signed main()
{
n = read(), m = read();
for(int i = 1; i <= n; ++i) a[i] = read();
Seg::Build(1, 1, n);
for(int i = 1, opt, l, r, k; i <= m; ++i) {
opt = read(), l = read(), r = read();
if(opt == 1) printf("%lld\n", Seg::Get_Sum(1, 1, n, l, r));
else if(opt == 2) k = read(), Seg::Sec_Mod(1, 1, n, l, r, k);
else Seg::Change(1, 1, n, l, l, r);
}
return 0;
}
CF703D Mishka and Interesting sum
Description
CF703D Mishka and Interesting sum
Solution
- 树状数组
Code
/*
Work by: Suzt_ilymics
Knowledge: 树状数组
Time: O(能过)
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define LL long long
#define orz cout<<"lkp AK IOI!"<<endl
using namespace std;
const int MAXN = 1e6+5;
const int INF = 1e9+7;
const int mod = 1e9+7;
int read();
struct Ques{
int l, r, bh;
void Read(int x) { l = read(), r = read(), bh = x; }
bool operator < (const Ques &b) const { return r < b.r; }
}q[MAXN];
int n, m, now_ = 1;
int a[MAXN], date[MAXN], date_num = 0;
int tree[MAXN << 2], sum[MAXN], pre[MAXN], head[MAXN];
int ans[MAXN];
int read(){
int s = 0, f = 0;
char ch = getchar();
while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
return f ? -s : s;
}
int lowbit(int x) { return x & (-x); }
void Add(int x, int val) { for(int i = x; i <= n; i += lowbit(i)) tree[i] ^= val; }
int Query(int x) {
int res = 0;
for(int i = x; i; i -= lowbit(i)) res ^= tree[i];
return res;
}
int main()
{
n = read();
for(int i = 1; i <= n; ++i) a[i] = date[i] = read();
sort(date + 1, date + n + 1), date[0] = -INF;
for(int i = 1; i <= n; ++i) if(date[i] != date[i - 1]) date[++ date_num] = date[i];
for(int i = 1; i <= n; ++i) a[i] = lower_bound(date + 1, date + date_num + 1, a[i]) - date;
for(int i = 1; i <= n; ++i) {
sum[i] = sum[i - 1] ^ date[a[i]];
pre[i] = head[a[i]], head[a[i]] = i;
}
m = read();
for(int i = 1; i <= m; ++i) q[i].Read(i);
sort(q + 1, q + m + 1);
for(int i = 1; i <= m; ++i) {
while(now_ <= q[i].r) {
if(pre[now_]) Add(pre[now_], date[a[now_]]);
Add(now_, date[a[now_]]);
++ now_;
}
ans[q[i].bh] = (Query(q[i].r) ^ Query(q[i].l - 1)) ^ sum[q[i].r] ^ sum[q[i].l - 1];
}
for(int i = 1; i <= m; ++i) printf("%d\n", ans[i]);
return 0;
}
P4211 [LNOI2014]LCA
题目传送
Solution:
嗯……主要在题目转化方面
还要做
一个很神奇的思路,差分!
考虑离线处理。
区间
误区:一开始想成了树上差分,结果把整个题解区翻了一遍都没看懂做法
/*
Work by: Suzt_ilymics
Problem: 不知名屑题
Knowledge: 垃圾算法
Time: O(能过)
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#define LL long long
#define orz cout<<"lkp AK IOI!"<<endl
using namespace std;
const int MAXN = 1e6+5;
const int INF = 1e9+7;
const int mod = 201314;
struct node {
int pre, z;
bool type;
};
int n, m;
int dep[MAXN], fath[MAXN], top[MAXN], dfn[MAXN], siz[MAXN], son[MAXN], cnt = 0;
int ans[MAXN];
vector <node> a[MAXN];
int read(){
int s = 0, f = 0;
char ch = getchar();
while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
return f ? -s : s;
}
namespace Seg {
#define lson i << 1
#define rson i << 1 | 1
struct Tree{
int len, lazy, sum;
}tree[MAXN << 2];
void Push_up(int i) { tree[i].sum = tree[lson].sum + tree[rson].sum; }
void Build(int i, int l, int r) {
tree[i].len = r - l + 1;
if(l == r) return ;
int mid = (l + r) >> 1;
Build(lson, l, mid), Build(rson, mid + 1, r);
Push_up(i);
}
void Push_down(int i) {
if(tree[i].lazy) {
tree[lson].lazy += tree[i].lazy;
tree[rson].lazy += tree[i].lazy;
tree[lson].sum += tree[lson].len * tree[i].lazy;
tree[rson].sum += tree[rson].len * tree[i].lazy;
tree[i].lazy = 0;
}
}
void Add(int i, int l, int r, int L, int R, int val) {
if(L <= l && r <= R) {
tree[i].sum += tree[i].len * val;
tree[i].lazy += val;
return ;
}
Push_down(i);
int mid = (l + r) >> 1;
if(mid >= L) Add(lson, l, mid, L, R, val);
if(mid < R) Add(rson, mid + 1, r, L, R, val);
Push_up(i);
}
int Query(int i, int l, int r, int L, int R) {
// cout<<l<<" "<<r<<" "<<L<<" "<<R<<"\n";
if(L <= l && r <= R) return tree[i].sum;
Push_down(i);
int mid = (l + r) >> 1, ans = 0;
if(mid >= L) ans += Query(lson, l, mid, L, R);
if(mid < R) ans += Query(rson, mid + 1, r, L, R);
return ans;
}
}
namespace Cut {
struct edge { int to, nxt; }e[MAXN << 1];
int head[MAXN], num_edge = 1;
void add_edge(int from, int to) { e[++num_edge] = (edge){to, head[from]}, head[from] = num_edge; }
void dfs(int u, int fa) {
// cout<<"dfs1: "<<u<<endl;
fath[u] = fa, siz[u] = 1, dep[u] = dep[fa] + 1;
for(int i = head[u]; i; i = e[i].nxt) {
int v = e[i].to;
if(v == fa) continue;
dfs(v, u);
siz[u] += siz[v];
if(siz[v] > siz[son[u]]) son[u] = v;
}
}
void dfs2(int u, int tp) {
// cout<<"dfs2: "<<u<<endl;
top[u] = tp, dfn[u] = ++cnt;
if(son[u]) dfs2(son[u], tp);
for(int i = head[u]; i; i = e[i].nxt) {
int v = e[i].to;
if(v == fath[u] || v == son[u]) continue;
dfs2(v, v);
}
}
void Add(int x, int y) {
while(top[x] != top[y]) {
if(dep[top[x]] < dep[top[y]]) swap(x, y);
Seg::Add(1, 1, n, dfn[top[x]], dfn[x], 1);
x = fath[top[x]];
}
if(dep[x] > dep[y]) swap(x, y);
Seg::Add(1, 1, n, dfn[x], dfn[y], 1);
}
int Query(int x, int y) {
int ans = 0;
while(top[x] != top[y]) {
if(dep[top[x]] < dep[top[y]]) swap(x, y);
ans += Seg::Query(1, 1, n, dfn[top[x]], dfn[x]);
x = fath[top[x]];
}
if(dep[x] > dep[y]) swap(x, y);
ans += Seg::Query(1, 1, n, dfn[x], dfn[y]);
return ans;
}
}
int main()
{
// freopen("P4211_1.in","r",stdin);
// freopen("P4211_1.out","w",stdout);
n = read(), m = read();
for(int i = 2, x; i <= n; ++i) x = read() + 1, Cut::add_edge(i, x), Cut::add_edge(x, i);
Cut::dfs(1, 0), Cut::dfs2(1, 1), Seg::Build(1, 1, n);
for(int i = 1, l, r, z; i <= m; ++i) {
l = read() + 1, r = read() + 1, z = read() + 1;
a[l - 1].push_back((node){i, z, 0});
a[r].push_back((node){i, z, 1});
}
for(int i = 1; i <= n; ++i) {
Cut::Add(1, i);
for(int j = 0; j < a[i].size(); ++j) {
if(a[i][j].type) {
// cout<<"type1: "<<a[i][j].z<<"\n";
ans[a[i][j].pre] = (ans[a[i][j].pre] + Cut::Query(1, a[i][j].z)) % mod;
} else {
// cout<<"type0: "<<a[i][j].z<<"\n";
ans[a[i][j].pre] = (ans[a[i][j].pre] - Cut::Query(1, a[i][j].z) + mod) % mod;
}
}
}
for(int i = 1; i <= m; ++i) printf("%d\n", ans[i]);
return 0;
}
CF920F SUM and REPLACE
Description
预处理出每个数的约数个数。
线性筛和暴力都能很快的筛出来。
然后修改的时候就暴力修改。
因为对于一个很大的数
/*
Work by: Suzt_ilymics
Knowledge: ??
Time: O(??)
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define int long long
#define LL long long
#define orz cout<<"lkp AK IOI!"<<endl
using namespace std;
const int MAXN = 1e6+5;
const int INF = 1e9+7;
const int mod = 1e9+7;
int n, m;
int a[MAXN], cnt[MAXN];
int read(){
int s = 0, f = 0;
char ch = getchar();
while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
return f ? -s : s;
}
namespace Seg{
#define lson i << 1
#define rson i << 1 | 1
struct Tree{ int lazy, max, sum; }tree[MAXN << 2];
void Push_up(int i) {
tree[i].max = max(tree[lson].max, tree[rson].max);
tree[i].sum = tree[lson].sum + tree[rson].sum;
}
void Build(int i, int l, int r) {
if(l == r) { tree[i].sum = tree[i].max = a[l]; return ; }
int mid = (l + r) >> 1;
Build(lson, l, mid), Build(rson, mid + 1, r);
Push_up(i);
}
void Change(int i, int l, int r, int L, int R) {
if(tree[i].max <= 2) return ;
if(l == r) { tree[i].sum = tree[i].max = cnt[tree[i].sum]; return ; }
int mid = (l + r) >> 1;
if(mid >= L) Change(lson, l, mid, L, R);
if(mid < R) Change(rson, mid + 1, r, L, R);
Push_up(i);
}
int Query(int i, int l, int r, int L, int R) {
if(L <= l && r <= R) return tree[i].sum;
int mid = (l + r) >> 1, ans = 0;
if(mid >= L) ans += Query(lson, l, mid, L, R);
if(mid < R) ans += Query(rson, mid + 1, r, L, R);
return ans;
}
}
void Init(int limit) {
for(int i = 1; i <= limit; ++i)
for(int j = i; j <= limit; j += i)
++ cnt[j];
}
signed main()
{
Init(1000000);
n = read(), m = read();
for(int i = 1; i <= n; ++i) a[i] = read();
Seg::Build(1, 1, n);
for(int i = 1, opt, l, r; i <= m; ++i) {
opt = read(), l = read(), r = read();
if(opt == 1) Seg::Change(1, 1, n, l, r);
else printf("%lld\n", Seg::Query(1, 1, n, l, r));
}
return 0;
}