美妙的神奇的线段树
线段树
板子
注意要用结构体
如:当维护信息很多或有绝对值,这样很好写(下面是含区间绝对值代码)注释的为区间加
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iomanip>
#include<bits/stdc++.h>
#define int long long
#define lson x<<1
#define rson x<<1|1
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct node{//结构体线段树只改四(五)个地方
int sum;//代表区间相邻两数差的绝对值的和
int lv;//最左边的数是什么
int rv;//最右边的数是什么//维护的值是第一个改动
int size;
int add;
node(){
sum=size=add=0;
}
void chu(int v){
// sum=v;
// size=1;区间加
lv=rv=v;//初始化第二改动
sum=0;
}
}tr[400005];
int a[100005],n,q;
node operator+(const node &l,const node &r){
node ans;
// ans.sum=l.sum+r.sum;
// ans.size=l.size+r.size;
ans.sum=l.sum+r.sum+abs(l.rv-r.lv);//重载运算符第三改动
ans.lv=l.lv;
ans.rv=r.rv;
return ans;
}
void color(int l,int r,int x,int v){
tr[x].add+=v;
// tr[x].sum+=tr[x].size*v;//区间操作第四改
tr[x].lv+=v;
tr[x].rv+=v;
}
void pushdown(int l,int r,int x){
int mid=(l+r)>>1;
color(l,mid,lson,tr[x].add);
color(mid+1,r,rson,tr[x].add);
tr[x].add=0;
}
void build(int l,int r,int x){
if(l==r){
tr[x].chu(a[l]);
return ;
}
int mid=(l+r)>>1;
build(l,mid,lson);
build(mid+1,r,rson);
tr[x]=tr[lson]+tr[rson];
}
node query(int l,int r,int x,int nl,int nr){
if(nl<=l&&r<=nr) return tr[x];
pushdown(l,r,x);
int mid=(l+r)>>1;
if(nl<=mid){
if(mid<nr) return query(l,mid,lson,nl,nr)+query(mid+1,r,rson,nl,nr);
else return query(l,mid,lson,nl,nr);
}
else return query(mid+1,r,rson,nl,nr);
}
void modify(int l,int r,int x,int nl,int nr,int v){
if(nl<=l&&r<=nr){
color(l,r,x,v);
return ;
}
pushdown(l,r,x);
int mid=(l+r)>>1;
if(nl<=mid) modify(l,mid,lson,nl,nr,v);
if(mid<nr) modify(mid+1,r,rson,nl,nr,v);
tr[x]=tr[lson]+tr[rson];
}
signed main()
{
//freopen("filename.in", "r", stdin);
//freopen("filename.out", "w", stdout);
n=read(),q=read();
for(int i=1;i<=n;i++) a[i]=read();
build(1,n,1);
while(q--){
int opt,x,y,k;
cin>>opt>>x>>y;
if(opt==1){
cin>>k;
modify(1,n,1,x,y,k);
}
else cout<<query(1,n,1,x,y).sum<<'\n';
}
return 0;
}
例题:
1.区间平方和
把平方拆开
2.线段树2 乘+加
先乘后加(如有推平放最前面)
add,mul写一个函数
3.区间等差数列
tag记录x,y(首项末项)
标记下放时,发现合并后仍然为等差数列
x,y直接相加,sum=sum+(x+x+(siz-1))ysiz/2
对于左右儿子
左儿子x,y直接操作
右儿子y不变,x为x+siz[ls]*y
操作即可
4.逆序对
给定N个数,M次询问,每次给定i,要求把i往后的所有数中<=a[i]的数拿出来排序,放回原数组
每次询问求操作后的数组逆序对的个数
M,N,a[i]<=1e5
首先,如果按照题目模拟排序,时间复杂度O(mnlogn),炸
所以考虑直接求逆序对个数
发现排序后,逆序对个数只少不多
考虑记录一个f[i]表示i后与多少个数比a[i]小
f[i]的和即为逆序对个数
发现每次操作拿出的数的f[i]都会变成0
所以新的逆序对即为原有答案将拿出的数的f[i]的和减掉即可
而这个暴力也会炸
所以考虑线段树
记录sum,minn
每次查询i~n的minn,若minn<=a[i],说明区间有值小于a[i],递归
找到一个节点,f[j]=0,a[j]=正无穷(防止重复计数,确保复杂度)
因为每个点只访问一次
时间复杂度O(nlogn)
5.区间AND区间除区间求和

但本题卡常
完整做法如下

6.Friends
7.Blog Post Rating

可持久化数据结构
可持久化并查集
支持撤销
但不可用路径压缩
板子可持久化并查集
可持久化线段树
tag不下传
Code
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iomanip>
#include<bits/stdc++.h>
#define int long long
#define lson x<<1
#define rson x<<1|1
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct node{
int sum;
int l,r;
node(){
sum=l=r=0;
}
}tr[20000005];//大小为maxn*logn
int cnt;//时间戳
void update(int x){
tr[x].sum=tr[tr[x].l].sum+tr[tr[x].r].sum;
}
int a[1000005],n,q,rt[1000005];
int build(int l,int r){//当前区间l~r,返回这段区间对应的节点编号(时间戳)
cnt++;
int x=cnt;
if(l==r){
tr[x].sum=a[l];
return x;
}
int mid=(l+r)>>1;
tr[x].l=build(l,mid);
tr[x].r=build(mid+1,r);
update(x);
return x;
}
int query(int l,int r,int x,int nl,int nr){//询问代码几乎不变(区间加)
if(nl<=l&&r<=nr) return tr[x].sum;
int mid=(l+r)>>1;
if(nl<=mid){
if(mid<nr) return query(l,mid,tr[x].l,nl,nr)+query(mid+1,r,tr[x].r,nl,nr);
else return query(l,mid,tr[x].l,nl,nr);
}
else return query(mid+1,r,tr[x].r,nl,nr);
}
int modify(int l,int r,int x,int p,int v){//单点加v,新建一颗线段树修改,返回修改后新节点编号(时间戳)
cnt++;
int pq=cnt;
tr[pq]=tr[x];
if(l==r){
tr[pq].sum+=v;
return pq;
}
int mid=(l+r)>>1;
if(p<=mid) tr[pq].l=modify(l,mid,tr[pq].l,p,v);
else tr[pq].l=modify(mid+1,r,tr[pq].r,p,v);
update(pq);
return pq;
}
signed main()
{
//freopen("filename.in", "r", stdin);
//freopen("filename.out", "w", stdout);
n=read(),q=read();
for(int i=1;i<=n;i++) a[i]=read();
rt[0]=build(1,n);//rt[i]表示第i次操作后根节点是谁
for(int i=1;i<=q;i++){
int k=read(),opt=read();
if(opt==1){
int p=read(),v=read();
rt[i]=modify(1,n,rt[k],p,v);
}
else{
int l=read();
//int r=read();
//cout<<query(1,n,rt[k],l,r)<<'\n';//区间加
cout<<query(1,n,rt[k],l,l)<<'\n';
rt[i]=rt[k];
}
}
return 0;
}
平衡树(两者会一个即可)
Spaly
旋转
Treap
分裂合并
https://www.luogu.com.cn/problem/P3215
平衡树板子

浙公网安备 33010602011771号