数据结构
1. 并查集
- 合并两个集合
- 查询一个元素的祖宗节点
- 询问两个元素是否在同一个集合中
具有信息传递性,对称性
// 朴素并查集:
int p[N]; //存储每个点的祖宗节点
// 返回x的祖宗节点 + 路径压缩
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ ) p[i] = i;
// 合并a和b所在的两个集合:
p[find(a)] = find(b);
优化方式
- 路径压缩 O ( log n ) O(\log n) O(logn)
- 按秩合并 O ( log n ) O(\log n) O(logn)
- 二者都采用 O ( α ( n ) ) O(\alpha(n)) O(α(n))
带权并查集
- 记录每个集合的大小 绑定到根结点
- 每个节点到根结点的距离(相对距离) 绑定到每个元素上
// 维护集合size的并查集:
int p[N], size[N];
//p[]存储每个点的祖宗节点, size[]只有祖宗节点的有意义,表示祖宗节点所在集合中的点的数量
// 返回x的祖宗节点
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ )
{
p[i] = i;
size[i] = 1;
}
// 合并a和b所在的两个集合:
size[find(b)] += size[find(a)];
p[find(a)] = find(b);
// 维护到祖宗节点距离的并查集:
int p[N], d[N];
//p[]存储每个点的祖宗节点, d[x]存储x到p[x]的距离
// 返回x的祖宗节点
int find(int x)
{
if (p[x] != x)
{
int root = find(p[x]);
d[x] += d[p[x]];
p[x] = root;
}
return p[x];
}
// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ )
{
p[i] = i;
d[i] = 0;
}
// 合并a和b所在的两个集合:
p[find(a)] = find(b);
d[find(a)] = distance; // 根据具体问题,初始化find(a)的偏移量
扩展域并查集(枚举)
并查集解决的是连通性(无向图联通分量)和传递性(家谱关系)问题,并且可以动态的维护。抛开格子不看,任意一个图中,增加一条边形成环当且仅当这条边连接的两点已经联通,于是可以将点分为若干个集合,每个集合对应图中的一个连通块。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 40050;
int n, m, res = 1e9;
int a[MAXN], fa[MAXN];
int transfer(int x, int y) // 二维转换为一维
{
return (x-1) * n + y;
}
void init()
{
for(int i = 1;i <= n*n;i++)
{
fa[i] = i;
}
}
int find(int x)
{
if(x != fa[x]) fa[x] = find(fa[x]);
return fa[x];
}
int main()
{
scanf("%d%d",&n,&m);
init();
for(int i = 1;i <= m;i++)
{
int x, y;
char op;
scanf("%d %d %c",&x,&y,&op);
int res1, res2;
res1 = transfer(x,y);
if(op == 'D') res2 = transfer(x+1,y);
else if(op == 'R') res2 = transfer(x,y+1);
int u = find(res1), v = find(res2);
if(u == v) {
res = min(res,i);
}
else {
fa[u] = v;
}
}
if(res != 1e9) printf("%d\n",res);
else puts("draw");
return 0;
}
并查集+01背包
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 10010;
int fa[MAXN], c[MAXN], d[MAXN], dp[MAXN];
int n, m, w, id;
struct node{
int c, d;
}Node[MAXN];
int find(int x)
{
if(x != fa[x]) fa[x] = find(fa[x]);
return fa[x];
}
void init()
{
for(int i = 1;i <= n;i++) fa[i] = i;
}
int main()
{
scanf("%d%d%d",&n,&m,&w);
init();
for(int i = 1;i <= n;i++)
{
scanf("%d%d",&c[i],&d[i]);
}
for(int i = 1;i <= m;i++)
{
int a, b;
scanf("%d%d",&a,&b);
int u = find(a), v = find(b);
if(u != v)
{
fa[v] = u;
c[u] += c[v];
d[u] += d[v];
}
}
for(int i = 1;i <= n;i++) {
if(i == fa[i])
{
Node[id].c = c[i];
Node[id].d = d[i];
id++;
}
}
for(int i = 0;i < id;i++)
{
for(int j = w;j >= Node[i].c;j--)
{
dp[j] = max(dp[j],dp[j-Node[i].c]+Node[i].d);
}
}
int ans = dp[w];
printf("%d\n",ans);
return 0;
}
并查集+离散化
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
const int MAXN = 2e6+50;
int n, T, id, cnt;
int fa[MAXN];
vector<pair<int,int>>equ, iequ;
int all[MAXN];
void init()
{
for(int i = 0;i <= cnt;i++) fa[i] = i;
}
int search(int x)
{
int res = lower_bound(all,all+cnt,x)-all;
return res;
}
int find(int x)
{
if(x != fa[x]) fa[x] = find(fa[x]);
return fa[x];
}
int main()
{
scanf("%d",&T);
while(T--)
{
equ.clear();
iequ.clear();
bool flag = true;
scanf("%d",&n);
for(int k = 0;k < n;k++)
{
int i, j, e;
scanf("%d%d%d",&i,&j,&e);
if(e == 1) equ.push_back(make_pair(i,j));
else iequ.push_back(make_pair(i,j));
all[id++] = i;
all[id++] = j;
}
sort(all,all+id);
cnt = unique(all,all+id)-all;
init();
for(auto i : equ)
{
int x = search(i.first), y = search(i.second);
int u = find(x), v = find(y);
fa[u] = v;
}
for(auto i : iequ)
{
int x = search(i.first), y = search(i.second);
int u = find(x), v = find(y);
if(u == v) {
flag = false;
break;
}
}
if(flag) puts("YES");
else puts("NO");
}
return 0;
}
带边权的并查集
利用前缀和的思想, S i = a 1 + a 2 + ⋯ + a i S_i = a_1 + a_2 + \dots + a_i Si=a1+a2+⋯+ai
若 S [ L , R ] S[L,R] S[L,R]中有奇数个1,则 S R S_R SR与 S L − 1 S_{L-1} SL−1奇偶性不同
若 S [ L , R ] S[L,R] S[L,R]中有偶数个1,则 S R S_R SR与 S L − 1 S_{L-1} SL−1奇偶性相同
采用离散化处理,只需找出第一次矛盾的地方
带权并查集: d [ x ] = 0 d[x]=0 d[x]=0表示与父节点同类,1表示不同类。描述的是一种相对的关系。(通过对2取模)
有几类就对几取模,通过两个点与根节点的长度来判断y两者之间关系。
判断x与y的关系, ( d [ x ] + d [ y ] ) m o d 2 = 0 (d[x]+d[y])mod2 = 0 (d[x]+d[y])mod2=0 说明 x x x与 y y y同类
① x,y同一类
- f a [ x ] = f a [ y ] fa[x]=fa[y] fa[x]=fa[y] ( d [ x ] + d [ y ] ) m o d 2 = 0 (d[x]+d[y]) \mod 2= 0 (d[x]+d[y])mod2=0 不矛盾 ( d [ x ] + d [ y ] ) m o d 2 = 1 (d[x]+d[y])\mod 2= 1 (d[x]+d[y])mod2=1 矛盾
- f a [ x ] ≠ f a [ y ] fa[x] \ne fa[y] fa[x]=fa[y] d [ f a [ x ] ] = ( d [ x ] + d [ y ] ) m o d 2 d[fa[x]] = (d[x] + d[y]) \mod 2 d[fa[x]]=(d[x]+d[y])mod2
① x,y不同一类
- f a [ x ] = f a [ y ] fa[x]=fa[y] fa[x]=fa[y] ( d [ x ] + d [ y ] ) m o d 2 = 1 (d[x]+d[y]) \mod 2= 1 (d[x]+d[y])mod2=1 不矛盾 ( d [ x ] + d [ y ] ) m o d 2 = 0 (d[x]+d[y])\mod 2= 0 (d[x]+d[y])mod2=0 矛盾
- f a [ x ] ≠ f a [ y ] fa[x] \ne fa[y] fa[x]=fa[y] d [ f a [ x ] ] = ( d [ x ] + d [ y ] + 1 ) m o d 2 d[fa[x]] = (d[x] + d[y] + 1) \mod 2 d[fa[x]]=(d[x]+d[y]+1)mod2
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int MAXN = 10010;
int n, m;
int fa[MAXN], d[MAXN];
vector<int>all;
struct node{
int l, r, flag;
}N[MAXN];
vector<node>v;
int find(int x)
{
if(x != fa[x]) {
int root = find(fa[x]);
d[x] += d[fa[x]];
fa[x] = root;
}
return fa[x];
}
int search(int x)
{
return lower_bound(all.begin(),all.end(),x)-all.begin();
}
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1;i <= m;i++)
{
int l, r;
char op[7];
scanf("%d%d%s",&l,&r,op);
if(*op == 'e') v.push_back({l-1,r,0});
else v.push_back({l-1,r,1});
all.push_back(l-1);
all.push_back(r);
}
sort(all.begin(),all.end());
all.erase(unique(all.begin(),all.end()),all.end());
int ans = 0;
for(int i = 0;i < all.size();i++)
{
fa[i] = i;
d[i] = 0;
}
for(auto i : v)
{
int u = search(i.l), v = search(i.r);
if(i.flag)
{
int x = find(u), y = find(v);
if(x == y)
{
if((d[u] + d[v]) % 2 == 0) break;
}
else
{
fa[x] = y;
d[x] = (d[u] + d[v] + 1) % 2;
}
}
else
{
int x = find(u), y = find(v);
if(x == y)
{
if((d[u] + d[v]) % 2 == 1) break;
}
else
{
fa[x] = y;
d[x] = (d[u] + d[v]) % 2;
}
}
ans++;
}
printf("%d\n",ans);
return 0;
}
若要维护两两战舰之间的距离的话,复杂度为 O ( n 2 ) O(n^2) O(n2)级别。
因此统一维护该战舰到排头的距离
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 30010;
int T;
int fa[MAXN], d[MAXN], sz[MAXN];
int find(int x)
{
if(x != fa[x])
{
int root = find(fa[x]);
d[x] += d[fa[x]]; // 完成路径压缩
fa[x] = root;
}
return fa[x];
}
int main()
{
for(int i = 1;i <= 30000;i++)
{
fa[i] = i;
d[i] = 0; // 表示每个集合到根结点的距离
sz[i] = 1; // 表示每个集合大小
}
scanf("%d",&T);
while(T--)
{
char op[2];
int i, j;
scanf("%s%d%d",op,&i,&j);
if(*op == 'M')
{
int u = find(i), v = find(j);
fa[u] = v;
d[u] = sz[v]; // 每个顶点的距离加上原先的长度 等价于 排头结点加上集合大小
sz[v] += sz[u];
}
else
{
int u = find(i), v = find(j);
if(u != v) printf("-1\n");
else if(i == j) printf("0\n");
else printf("%d\n",abs(d[i]-d[j])-1);
}
}
return 0;
}
2. 树状数组
基本原理
- 快速求前缀和 O ( log n ) O(\log n) O(logn)
- 修改某一个数 O ( log n ) O(\log n) O(logn)
基于二进制优化
x = 2 i k + 2 i k = 1 + 2 i k − 2 + ⋯ + 2 i 1 log x ≥ i k ≥ i k − 1 ≥ i k − 2 ≥ ⋯ ≥ i 1 x = 2^{i_k}+2^{i_{k=1}}+2^{i_{k-2}}+\dots +2^{i_1} \qquad \qquad \log x \ge i_k \ge i_{k-1} \ge i_{k-2} \ge \dots \ge i_1 x=2ik+2ik=1+2ik−2+⋯+2i1logx≥ik≥ik−1≥ik−2≥⋯≥i1
① ( x − 2 i 1 , x ] (x-2^{i_1},x] (x−2i1,x]
② ( x − 2 i 1 − 2 i 2 , x − 2 i 1 ] (x-2^{i_1}-2^{i_2},x-2^{i_1}] (x−2i1−2i2,x−2i1]
③ ( x − 2 i 1 − 2 i 2 − 2 i 3 , x − 2 i 1 − 2 i 2 ] (x-2^{i_1}-2^{i_2}-2^{i_3},x-2^{i_1}-2^{i_2}] (x−2i1−2i2−2i3,x−2i1−2i2]
… \dots …
k ( x − 2 i 1 − 2 i 2 − ⋯ − 2 i k = 0 , x − 2 i 1 − 2 i 2 − ⋯ − 2 i k − 1 ] (x-2^{i_1}-2^{i_2}-\dots -2^{i_k} = 0,x-2^{i_1}-2^{i_2}-\dots -2^{i_{k-1}}] (x−2i1−2i2−⋯−2ik=0,x−2i1−2i2−⋯−2ik−1]
发现区间 ( L , R ] (L,R] (L,R]的区间长度为R二进制的最后一位1 所对应的次幂 l o w b i t ( R ) lowbit(R) lowbit(R)
区间为 [ R − l o w b i t ( R ) + 1 , R ] [R-lowbit(R)+1,R] [R−lowbit(R)+1,R]
记 c [ x ] c[x] c[x]为区间 a [ x − l o w b i t ( x ) + 1 , x ] a[x-lowbit(x)+1,x] a[x−lowbit(x)+1,x]所有数的和
c i = a i + a i − 1 + … … + a i − l o w b i t ( i ) + 1 c_i = a_i+a_{i-1}+……+a_{i-lowbit(i)+1} ci=ai+ai−1+……+ai−lowbit(i)+1
c[1] = a[1]
c[2] = a[2]+a[1]
c[3] = a[3]
c[4] = a[4]+a[3]+a[2]+a[1]
树状数组查询 O ( log n ) O(\log n) O(logn)
eg 查询a[1]+a[2]+……+a[7]
a[7] = c[7] a[6]+a[5] = c[6] a[4]+a[3]+a[2]+a[1] = c[4]
7 = ( 111 ) 2 (111)_2 (111)2 6 = ( 110 ) 2 (110)_2 (110)2 4 = ( 100 ) 2 (100)_2 (100)2 每次减去lowbit
// 查询a[1~x]的和
int sum(int x)
{
int ret = 0;
while(x)
{
ret += c[x];
x -= lowbit(x);
}
return ret;
}
树状数组修改 O ( log n ) O(\log n) O(logn)
x的父亲结点是x+lowbit(x)
每次修改会影响这个节点到根路径下的节点
// a[i] += k
void change(int i, int k)
{
while(i <= n)
{
c[i] += k;
i += lowbit(i);
}
}
lowbit(i)求法
inline int lowbit(int x)
{
return x & (-x);
}
inline int lowbit(int x)
{
return x & (-x);
}
// 查询a[1~x]的和
int sum(int x)
{
int ret = 0;
while(x)
{
ret += c[x];
x -= lowbit(x);
}
return ret;
}
// a[i] += k
void change(int i, int k)
{
while(i <= n)
{
c[i] += k;
i += lowbit(i);
}
}
树状数组维护出现过的数比其大or比其小的数量
从左向右依次遍历每个数a[i],使用树状数组统计在i位置之前所有比a[i]大的数的个数、以及比a[i]小的数的个数。统计完成后,将a[i]加入到树状数组。
从右向左依次遍历每个数a[i],使用树状数组统计在i位置之后所有比a[i]大的数的个数、以及比a[i]小的数的个数。统计完成后,将a[i]加入到树状数组。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 2e5+50;
int a[MAXN], t[MAXN];
int small[MAXN], big[MAXN];
int n;
inline int lowbit(int x)
{
return x & (-x);
}
int sum(int x)
{
int res = 0;
while(x)
{
res += t[x];
x -= lowbit(x);
}
return res;
}
void add(int i, int k)
{
while(i <= n)
{
t[i] += k;
i += lowbit(i);
}
}
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n;i++) scanf("%d",&a[i]);
for(int i = 1;i <= n;i++) // 顺序查找
{
big[i] = sum(n)-sum(a[i]); // 找比a[i]大的数
small[i] = sum(a[i]-1); // 找比a[i]小的数
add(a[i],1);
}
memset(t,0,sizeof(t));
ll resv = 0, resa = 0;
for(int i = n;i >= 1;i--)
{
resv += (ll)big[i]*(sum(n)-sum(a[i]));
resa += (ll)small[i]*sum(a[i]-1);
add(a[i],1);
}
printf("%lld %lld\n",resv,resa);
return 0;
}
扩展
区间修改与单点询问
- a [ L , R ] a[L,R] a[L,R]中每个元素+c
- 求 a [ x ] a[x] a[x]是多少
采用差分数组+树状数组
区间修改相当于差分数组中修改两个点
单点查询相当于差分数组求前缀和
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 100010;
int n, m;
ll a[MAXN], cf[MAXN], t[MAXN];
inline int lowbit(ll x)
{
return x & (-x);
}
ll sum(int x)
{
ll res = 0;
while(x)
{
res += t[x];
x -= lowbit(x);
}
return res;
}
void add(int x, int p)
{
while(x <= n)
{
t[x] += p;
x += lowbit(x);
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i++) scanf("%lld",&a[i]);
for(int i = 1;i <= n;i++) {
cf[i] = a[i]-a[i-1];
add(i,cf[i]);
}
for(int i = 1;i <= m;i++)
{
char op[2];
scanf("%s",op);
if(*op == 'Q')
{
int x;
scanf("%d",&x);
printf("%lld\n",sum(x));
}
else
{
int l, r, d;
scanf("%d%d%d",&l,&r,&d);
add(l,d);
add(r+1,-d);
}
}
return 0;
}
区间修改与区间求和
区间修改 : 差分数组 a [ L , R ] + = c a[L,R] += c a[L,R]+=c 等价于 b [ R + 1 ] − = c b [ L ] − = c b[R+1] -= c \qquad b[L] -= c b[R+1]−=cb[L]−=c
区间求和 : a [ 1 ] + a [ 2 ] + a [ 3 ] + ⋯ + a [ x ] a[1]+a[2]+a[3]+\dots+a[x] a[1]+a[2]+a[3]+⋯+a[x] = ∑ i = 1 x a [ i ] = ∑ i = 1 x ∑ j = 1 i b [ j ] \sum_{i=1}^xa[i] = \sum_{i=1}^{x} \sum_{j=1}^i b[j] ∑i=1xa[i]=∑i=1x∑j=1ib[j]
考虑补集
a [ 1 ] + a [ 2 ] + a [ 3 ] + ⋯ + a [ x ] = ( x + 1 ) ∗ ( b [ 1 ] + b [ 2 ] + ⋯ + b [ x ] ) − ( 1 ∗ b [ 1 ] + 2 ∗ b [ 2 ] + 3 ∗ b [ 3 ] + ⋯ + x ∗ b [ x ] ) a[1]+a[2]+a[3]+\dots+a[x] = (x+1)*(b[1]+b[2]+\dots+b[x])-(1*b[1]+2*b[2]+3*b[3]+\dots+x*b[x]) a[1]+a[2]+a[3]+⋯+a[x]=(x+1)∗(b[1]+b[2]+⋯+b[x])−(1∗b[1]+2∗b[2]+3∗b[3]+⋯+x∗b[x])
前面为 b [ i ] b[i] b[i]的前缀和,后面为 i ∗ b [ i ] i*b[i] i∗b[i]的前缀和
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 1e5+50;
int n, m;
ll a[MAXN], t[MAXN], cf[MAXN], ti[MAXN];
inline int lowbit(int x)
{
return x & (-x);
}
ll sum(int x)
{
ll res = 0;
while(x)
{
res += t[x];
x -= lowbit(x);
}
return res;
}
ll sumi(int x)
{
ll res = 0;
while(x)
{
res += ti[x];
x -= lowbit(x);
}
return res;
}
void add(int x, ll p)
{
while(x <= n)
{
t[x] += p;
x += lowbit(x);
}
}
void addi(int x, ll p)
{
while(x <= n)
{
ti[x] += p;
x += lowbit(x);
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i++) scanf("%lld",&a[i]);
for(int i = 1;i <= n;i++) {
cf[i] = a[i] - a[i-1];
add(i,cf[i]);
addi(i,i*cf[i]);
}
for(int i = 1;i <= m;i++)
{
char op[2];
scanf("%s",op);
if(*op == 'Q')
{
int l, r;
scanf("%d%d",&l,&r);
ll ans = ((r+1)*(sum(r))-sumi(r)) - ((l)*sum(l-1) - sumi(l-1));
printf("%lld\n",ans);
}
else
{
int l, r, d;
scanf("%d%d%d",&l,&r,&d);
add(l,d);
add(r+1,-d);
addi(l,(ll)d*l);
addi(r+1,-(ll)d*(r+1));
}
}
return 0;
}
每次查找第k小的数
并删除其
可用平衡树做。
若用树状数组,则将其全部初始化为1,每次二分查找sum(x)=k的最小x,将其减一
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 1e5+50;
int a[MAXN], t[MAXN], ans[MAXN];
int n;
inline int lowbit(int x)
{
return x & (-x);
}
int sum(int x)
{
int res = 0;
while(x)
{
res += t[x];
x -= lowbit(x);
}
return res;
}
void add(int i, int k)
{
while(i <= n)
{
t[i] += k;
i += lowbit(i);
}
}
int main()
{
scanf("%d",&n);
a[1] = 0;
for(int i = 2;i <= n;i++) scanf("%d",&a[i]);
for(int i = 1;i <= n;i++) add(i,1);
for(int i = n;i >= 1;i--)
{
int l = 1, r = n;
while(l < r)
{
int mid = (l+r) >> 1;
if(sum(mid) < a[i]+1) l = mid+1;
else r = mid;
}
ans[i] = l;
add(l,-1);
}
for(int i = 1;i <= n;i++) printf("%d\n",ans[i]);
return 0;
}
二维树状数组
// a[x][y] += z;
int update(int x, int y, int z)
{
int i = x;
while(i <= n)
{
int j = y;
while(j <= m)
{
c[i][j] += z;
j += lowbit(j);
}
i += lowbit(i);
}
}
// a[1][1] + …… + a[1][y] + a[2][1] + …… a[2][n] + …… +a[x][1] + …… + a[x][y]
int sum(int x, int y)
{
int res = 0, i = x;
while(i > 0)
{
j = y;
while(j > 0)
{
res += c[i][j];
j -= lowbit(j);
}
i -= lowbit(i);
}
return res;
}
3. 线段树
一个完全二叉树,每个节点代表一个区间。
区间 [ L , R ] [L,R] [L,R]被分为 [ L , m i d ] , [ m i d + 1 , R ] [L,mid],[mid+1,R] [L,mid],[mid+1,R] m i d = ⌊ L + R 2 ⌋ mid = \lfloor \frac{L+R}{2} \rfloor mid=⌊2L+R⌋
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u52lrHCn-1633831709717)(C:/Users/DELL/AppData/Roaming/Typora/typora-user-images/image-20210322084255533.png)]
线段树 当前结点x
父节点 ⌊ x 2 ⌋ \lfloor \frac{x}{2} \rfloor ⌊2x⌋
左儿子 x < < 1 x << 1 x<<1
右儿子 x < < 1 ∣ 1 x << 1 | 1 x<<1∣1
最大范围 n < < 2 n << 2 n<<2
五个操作
- pushup
- pushdown
- build
- modify
- query
node结构体
struct node{
int l, r;
ll sum, lazy; // 区间[l,r]的和
};
int n, m;
int w[MAXN];
node tr[MAXN << 2];
pushup
// 求父节点的和
void pushup(int u)
{
tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}
pushdown
void pushdown(int u) // 将懒标记下传
{
ll lazy = tr[u].lazy;
if(lazy)
{
tr[u << 1].lazy += lazy;
tr[u << 1 | 1].lazy += lazy;
tr[u << 1].sum += (tr[u << 1].r - tr[u << 1].l + 1) * lazy;
tr[u << 1 | 1].sum += (tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1)*lazy;
tr[u].lazy = 0; // 父节点的懒标记重置为0
}
}
建树
//build(1,1,n) 建立区间1到n的线段树
void build(int u, int l, int r)
{
if(l == r)
{
tr[u] = {l,r,w[r]};
return;
}
else
{
tr[u] = {l,r,0};
int mid = (l+r) >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid+1, r);
pushup(u);
}
}
询问
// 求区间和[l,r]
// 调用(1,l,r)
ll query(int u, int l, int r)
{
if(l <= tr[u].l && tr[u].r <= r) return tr[u].sum;
else
{
pushdown(u); // 下放懒标记
int mid = (tr[u].l + tr[u].r) >> 1;
ll sum = 0;
if(l <= mid) sum += query(u << 1, l, r);
if(r > mid) sum += query(u << 1 | 1, l, r);
return sum;
}
}
单点修改
// 调用(1,x,v)
// a[x] += v
void modify(int u, int x, int v)
{
if(tr[u].l == tr[u].r) tr[u].sum += v;
else
{
int mid = (tr[u].l + tr[u].r) >> 1;
if(x <= mid) modify(u << 1, x, v);
else modify(u << 1|1, x, v);
pushup(u);
}
}
区间修改
// a[l]~a[r] += d
void change(int u, int l, int r, int d) // 区间修改
{
if(l <= tr[u].l && tr[u].r <= r) //完全覆盖父节点,则标记一下懒惰标记即可,先不着急往下传,求和需要用到时再往下传
{
tr[u].lazy += d;
tr[u].sum += (tr[u].r - tr[u].l + 1) * d;
return;
}
else
{
pushdown(u);
int mid = (tr[u].l + tr[u].r) >> 1;
if(l <= mid) change(u << 1, l, r, d);
if(r > mid) change(u << 1 | 1, l, r, d);
pushup(u);
}
}
struct node{
int l, r;
ll sum, lazy; // 区间[l,r]的和
};
int n, m;
int w[MAXN];
node tr[MAXN << 2];
// 求父节点的和
void pushup(int u)
{
tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}
void pushdown(int u) // 将懒标记下传
{
ll lazy = tr[u].lazy;
if(lazy)
{
tr[u << 1].lazy += lazy;
tr[u << 1 | 1].lazy += lazy;
tr[u << 1].sum += (tr[u << 1].r - tr[u << 1].l + 1) * lazy;
tr[u << 1 | 1].sum += (tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1)*lazy;
tr[u].lazy = 0; // 父节点的懒标记重置为0
}
}
//build(1,1,n) 建立区间1到n的线段树
void build(int u, int l, int r)
{
if(l == r)
{
tr[u] = {l,r,w[r]};
return;
}
else
{
tr[u] = {l,r,0};
int mid = (l+r) >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid+1, r);
pushup(u);
}
}
// 求区间和[l,r]
// 调用(1,l,r)
ll query(int u, int l, int r)
{
if(l <= tr[u].l && tr[u].r <= r) return tr[u].sum;
else
{
pushdown(u); // 下放懒标记
int mid = (tr[u].l + tr[u].r) >> 1;
ll sum = 0;
if(l <= mid) sum += query(u << 1, l, r);
if(r > mid) sum += query(u << 1 | 1, l, r);
return sum;
}
}
// 调用(1,x,v)
// a[x] += v
void modify(int u, int x, int v)
{
if(tr[u].l == tr[u].r) tr[u].sum += v;
else
{
int mid = (tr[u].l + tr[u].r) >> 1;
if(x <= mid) modify(u << 1, x, v);
else modify(u << 1|1, x, v);
pushup(u);
}
}
// a[l]~a[r] += d
void change(int u, int l, int r, int d) // 区间修改
{
if(l <= tr[u].l && tr[u].r <= r) //完全覆盖父节点,则标记一下懒惰标记即可,先不着急往下传,求和需要用到时再往下传
{
tr[u].lazy += d;
tr[u].sum += (tr[u].r - tr[u].l + 1) * d;
return;
}
else
{
pushdown(u);
int mid = (tr[u].l + tr[u].r) >> 1;
if(l <= mid) change(u << 1, l, r, d);
if(r > mid) change(u << 1 | 1, l, r, d);
pushup(u);
}
}
维护区间最大值
模板题,单点修改,区间最大值。
区间最大值 = max(左区间最大值,右区间最大值)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 2e5+50;
struct node{
int l, r;
int v;
}tr[MAXN*4];
int m, p;
void pushup(int u)
{
tr[u].v = max(tr[u << 1].v,tr[u << 1 | 1].v);
}
void build(int u, int l, int r)
{
tr[u] = {l,r};
if(l == r) return;
int mid = l + r >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid+1, r);
}
int query(int u, int l, int r)
{
if(tr[u].l >= l && tr[u].r <= r) return tr[u].v;
else
{
int mid = tr[u].l + tr[u].r >> 1;
int v = 0;
if(l <= mid) v = query(u << 1, l, r);
if(r > mid) v = max(v,query(u << 1 | 1, l, r));
return v;
}
}
void modify(int u, int x, int v)
{
if(tr[u].l == tr[u].r) tr[u].v = v;
else
{
int mid = tr[u].l + tr[u].r >> 1;
if(x <= mid) modify(u << 1, x, v);
else modify(u << 1 | 1, x, v);
pushup(u);
}
}
int main()
{
scanf("%d%d",&m,&p);
build(1,1,m);
int n = 0, last = 0;
while(m--)
{
char op[3];
scanf("%s",op);
if(*op == 'A')
{
int t;
scanf("%d",&t);
modify(1,n+1,(t+last)%p);
n++;
}
else
{
int l;
scanf("%d",&l);
last = query(1,n-l+1,n);
printf("%d\n",last);
}
}
return 0;
}
①单点修改
②查询区间的最大子段和 = max(左区间的最大子段和,右区间的最大子段和,l_max+r_max)
结构体中保存,区间左右端点,最大连续子段和,最大前缀和,最大后缀和,区间和。
查询的时候输出结构体
传引用
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 5e5 + 50;
int n, m;
int a[MAXN];
struct ST{
int l, r;
int lmax, rmax, tmax, sum;
}tr[MAXN * 4];
void pushup(ST &u, ST &l, ST &r)
{
u.sum = l.sum + r.sum;
u.lmax = max(l.lmax, l.sum + r.lmax);
u.rmax = max(r.rmax, r.sum + l.rmax);
u.tmax = max(max(l.tmax, r.tmax), l.rmax + r.lmax);
}
void pushup(int u)
{
pushup(tr[u], tr[u << 1], tr[u << 1 | 1]);
}
void build(int u, int l, int r)
{
tr[u] = {l, r};
if(l == r)
{
tr[u].sum = tr[u].lmax = tr[u].rmax = tr[u].tmax = a[l];
return;
}
int mid = (l + r) >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
pushup(u);
}
ST query(int u, int l, int r)
{
if(tr[u].l >= l && tr[u].r <= r) {
return tr[u];
}
int mid = (tr[u].l + tr[u].r) >> 1;
if(r <= mid) return query(u << 1, l, r);
else if(l > mid) return query(u << 1 | 1, l, r);
else {
ST root;
auto left = query(u << 1, l, r);
auto right = query(u << 1 | 1, l, r);
pushup(root, left, right);
return root;
}
}
void change(int u, int x, int y)
{
if(tr[u].l == x && tr[u].r == x)
{
tr[u].sum = y;
tr[u].lmax = y;
tr[u].tmax = y;
tr[u].rmax = y;
return;
}
int mid = (tr[u].l + tr[u].r) >> 1;
if(x <= mid) change(u << 1, x, y);
else change(u << 1 | 1, x, y);
pushup(u);
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
build(1, 1, n);
for(int i = 0; i < m; i++)
{
int op;
scanf("%d", &op);
if(op == 1)
{
int l, r;
scanf("%d%d", &l, &r);
if(l > r) swap(l, r);
auto ans = query(1, l, r);
printf("%d\n", ans.tmax);
}
else {
int x, y;
scanf("%d%d", &x, &y);
change(1, x, y);
}
}
return 0;
}
懒标记(区间修改和区间查询)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 1e5+50;
int n, m;
ll a[MAXN];
struct node{
int l, r;
ll sum, lazy;
}tr[MAXN * 4];
void pushup(int u)
{
tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}
void pushdown(int u)
{
ll lazy = tr[u].lazy;
if(lazy)
{
tr[u << 1].lazy += lazy;
tr[u << 1 | 1].lazy += lazy;
tr[u << 1].sum += (tr[u << 1].r - tr[u << 1].l + 1) * lazy;
tr[u << 1 | 1].sum += (tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1) * lazy;
tr[u].lazy = 0;
}
}
void build(int u, int l, int r)
{
if(l == r) {
tr[u] = {l, r, a[l]};
return;
}
else {
tr[u] = {l, r};
int mid = (l + r) >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid+1, r);
pushup(u);
}
}
void change(int u, int l, int r, int d)
{
if(l <= tr[u].l && tr[u].r <= r)
{
tr[u].lazy += d;
tr[u].sum += (tr[u].r - tr[u].l + 1) * d;
return;
}
else
{
pushdown(u);
int mid = (tr[u].l + tr[u].r) >> 1;
if(l <= mid) change(u << 1, l, r, d);
if(r > mid) change(u << 1 | 1, l, r, d);
pushup(u);
}
}
ll query(int u, int l, int r)
{
if(l <= tr[u].l && tr[u].r <= r) return tr[u].sum;
else
{
pushdown(u);
int mid = (tr[u].l + tr[u].r) >> 1;
ll sum = 0;
if(l <= mid) sum = query(u << 1, l, r);
if(r > mid) sum += query(u << 1 | 1, l, r);
return sum;
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i++) scanf("%lld",&a[i]);
build(1,1,n);
while(m--)
{
char op[2];
scanf("%s",op);
if(*op == 'Q') {
int l, r;
scanf("%d%d",&l,&r);
ll ans = query(1,l,r);
printf("%lld\n",ans);
}
else {
int l, r, d;
scanf("%d%d%d",&l,&r,&d);
change(1,l,r,d);
}
}
return 0;
}
维护区间修改的乘法和加法。 需要加法和乘法的懒标记
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 1e5+50;
int n, p, T;
int a[MAXN];
struct node{
int l, r;
ll sum, add, mul; // mul是乘法懒标记
}tr[MAXN * 4];
void pushup(int u)
{
tr[u].sum = (tr[u << 1].sum + tr[u << 1 | 1].sum) % p;
}
void pushdown(int u)
{
ll add = tr[u].add, mul = tr[u].mul;
tr[u << 1].add = (tr[u << 1].add * mul + add) % p;
tr[u << 1 | 1].add = (tr[u << 1 | 1].add * mul + add) % p;
tr[u << 1].mul = tr[u << 1].mul * mul % p;
tr[u << 1 | 1].mul = tr[u << 1 | 1].mul * mul % p;
tr[u << 1].sum = (tr[u << 1].sum * mul + (tr[u << 1].r - tr[u << 1].l + 1) * add) % p;
tr[u << 1 | 1].sum = (tr[u << 1 | 1].sum * mul + (tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1) * add) % p;
tr[u].add = 0;
tr[u].mul = 1;
}
void build(int u, int l, int r)
{
if(l == r) {
tr[u] = {l,r,a[l],0,1};
return;
}
else
{
tr[u] = {l, r, 0, 0, 1};
int mid = (l + r) >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid+1, r);
pushup(u);
}
}
void change(int u, int l, int r, int c, int a)
{
if(l <= tr[u].l && tr[u].r <= r)
{
tr[u].sum = (tr[u].sum * c + a * (tr[u].r - tr[u].l + 1)) % p;
tr[u].add = (tr[u].add * c + a) % p;
tr[u].mul = (tr[u].mul * c) % p;
}
else
{
pushdown(u);
int mid = (tr[u].l + tr[u].r) >> 1;
if(l <= mid) change(u << 1, l, r, c, a);
if(r > mid) change(u << 1 | 1, l, r, c, a);
pushup(u);
}
}
int query(int u, int l, int r)
{
if(l <= tr[u].l && tr[u].r <= r)
{
return tr[u].sum;
}
pushdown(u);
int mid = (tr[u].l + tr[u].r) >> 1;
ll res = 0;
if(l <= mid)
{
res += query(u << 1, l, r);
res = res % p;
}
if(r > mid)
{
res += query(u << 1 | 1, l, r);
res = res % p;
}
return res;
}
int main()
{
scanf("%d%d",&n,&p);
for(int i = 1;i <= n;i++) scanf("%d",&a[i]);
build(1,1,n);
scanf("%d",&T);
while(T--)
{
int op, l, r, c;
scanf("%d",&op);
if(op == 1) {
scanf("%d%d%d",&l,&r,&c);
change(1, l, r, c, 0);
}
else if(op == 2) {
scanf("%d%d%d",&l,&r,&c);
change(1,l,r,1,c);
}
else{
scanf("%d%d",&l,&r);
int res = query(1,l,r);
printf("%d\n",res);
}
}
return 0;
}
4. 可持久化数据结构
可持久化前提:在本身的操作过程中,数据结构的拓扑结构不变
Trie的可持久化
把每次更新的版本都存储下来,可存储数据结构的所有历史版本。
只记录每一个版本与前一个版本不一样的地方。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 6e5+50;
int s[MAXN], tr[MAXN*25][2], idx, max_id[MAXN*25], root[MAXN];
// root存储每次版本根的信息, max_id存储当前子树中最大的左端点
int n, m;
// 插入操作, 第i个数, 第k位, p是旧版本, q是新版本
void insert(int i, int k, int p, int q)
{
// 如果插到最后一位
if(k < 0)
{
max_id[q] = i;
return;
}
int u = s[i] >> k & 1;
// 如果其他位有数,直接复制
if(p) tr[q][!u] = tr[p][!u];
// 当前位建立新结点
tr[q][u] = ++idx;
// 插入下一位
insert(i,k-1,tr[p][u], tr[q][u]);
// 递归后求子结点
max_id[q] = max(max_id[tr[q][0]],max_id[tr[q][1]]);
}
int query(int root, int c, int l)
{
int p = root;
for(int i = 23;i >= 0;i--)
{
int u = c >> i & 1;
if(max_id[tr[p][!u]] >= l) p = tr[p][!u]; // 如果当前位可以走到!u,就走
else p = tr[p][u];
}
return c ^ s[max_id[p]];
}
int main()
{
scanf("%d%d",&n,&m);
// 插入根
max_id[0] = -1;
root[0] = ++idx;
insert(0,23,0,root[0]);
for(int i = 1;i <= n;i++)
{
int x;
scanf("%d",&x);
s[i] = s[i-1] ^ x;
root[i] = ++idx;
insert(i,23,root[i-1],root[i]);
}
while(m--)
{
char op[2];
scanf("%s",op);
if(*op == 'A')
{
int x;
scanf("%d",&x);
n++;
s[n] = s[n-1] ^ x;
root[n] = ++idx;
insert(n,23,root[n-1],root[n]);
}
else
{
int l, r, x;
scanf("%d%d%d",&l,&r,&x);
int ans = query(root[r-1],s[n]^x,l-1); // 版本r-1,限制l-1
printf("%d\n",ans);
}
}
return 0;
}
线段树的可持久化 (主席树)
5. RMQ (ST表)
类似于倍增的动态规划
快速查询区间最大值,不支持修改
f [ i , j ] f[i, j] f[i,j]表示从 i i i开始,长度为 2 j 2^j 2j的区间最大值
初始化:
f [ i , j ] = m a x ( f [ i , j − 1 ] , f [ i + 2 j − 1 , j − 1 ] ) f[i,j] = max(f[i, j-1], f[i+2^{j-1}, j-1]) f[i,j]=max(f[i,j−1],f[i+2j−1,j−1])
查询 [ L , R ] [L,R] [L,R]:
R − L + 1 = l e n R - L + 1 = len R−L+1=len
k = ⌊ l o g 2 l e n ⌋ k = \lfloor log_2^{len} \rfloor k=⌊log2len⌋
f [ L , R ] = m a x ( f [ L , k ] , f [ R − 2 k + 1 , k ] f[L, R] = max(f[L, k], f[R-2^k+1, k] f[L,R]=max(f[L,k],f[R−2k+1,k]
模板
const int MAXN = 2e5+50, N = 18;
int n, m;
int a[MAXN], dp[MAXN][N];
void init()
{
for(int j = 0;j < N;j++)
{
for(int i = 1;i + (1 << j) - 1 <= n;i++)
{
if(j == 0) dp[i][j] = a[i];
else dp[i][j] = max(dp[i][j-1], dp[i + (1 << j - 1)][j-1]);
}
}
}
int query(int l, int r)
{
int len = r - l + 1;
int k = log(len) / log(2);
int ans = max(dp[l][k], dp[r - (1 << k) + 1][k]);
return ans;
}
1273. 天才的记忆 - AcWing题库(模板题)
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
const int MAXN = 2e5+50, N = 18;
int n, m;
int a[MAXN], dp[MAXN][N];
void init()
{
for(int j = 0;j < N;j++)
{
for(int i = 1;i + (1 << j) - 1 <= n;i++)
{
if(j == 0) dp[i][j] = a[i];
else dp[i][j] = max(dp[i][j-1], dp[i + (1 << j - 1)][j-1]);
}
}
}
int query(int l, int r)
{
int len = r - l + 1;
int k = log(len) / log(2);
int ans = max(dp[l][k], dp[r - (1 << k) + 1][k]);
return ans;
}
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n;i++) scanf("%d",&a[i]);
init();
scanf("%d",&m);
while(m--)
{
int l, r;
scanf("%d%d",&l,&r);
int ans = query(l, r);
printf("%d\n",ans);
}
return 0;
}