随笔- 181  文章- 2  评论- 0  阅读- 6743 

树状数组

树状数组1

题意:单点修改,求前缀和

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<string> VS;
typedef vector<int> VI;
typedef vector<vector<int>> VVI;
const int N = 2e5+10;
ll a[N],c[N];
int n;
inline int lowbit(int x) {return x&-x;}
inline void update(int x,ll a) {while(x<=n) c[x]+=a,x+=lowbit(x);}
inline ll query(int x) {ll res = 0;while(x) res+=c[x],x-=lowbit(x);return res;}
void solve()
{
	int q;
	cin>>n>>q;
	for(int i=1;i<=n;++i) 
	{
		cin>>a[i];
		update(i,a[i]);
	}
	for(int i=1;i<=q;++i) 
	{
		int op;
		cin>>op;
		if(op&1) 
		{
			int x,d;
			//将a[x]修改为d等价于a[x]+=d-a[x]
			cin>>x>>d;
			update(x,d-a[x]);
			a[x] = d;
		}
		else
		{
			int x;
			cin>>x;
			cout<<query(x)<<'\n';
		}
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T=1;
	//cin>>T;
	while(T--)
	{
		solve();
	}
}

树状数组2(bushi)

题意:区间改,区间查

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<string> VS;
typedef vector<int> VI;
typedef vector<vector<int>> VVI;
const int N = 2e5+10;
//用l,r表示在线段树在区间[1,n]上对应的左右端点
//用p<<1,p<<1|1表示线段树的左右儿子
//用x,y表示查询或修改的区间
int a[N];
int n,q;
struct node
{
	int l,r;
	ll sum,lazy;
}tr[N<<2];
void pushup(int p)
{
	tr[p].sum = tr[p<<1].sum+tr[p<<1|1].sum;
}
void pushdown(int p)
{
	if(!tr[p].lazy) return ;
	int l = tr[p].l, r = tr[p].r, m = l+(r-l)/2;
	ll lazy = tr[p].lazy;
	tr[p<<1].sum += lazy*(m-l+1);
	tr[p<<1|1].sum += lazy*(r-m);
	tr[p<<1].lazy += lazy;
	tr[p<<1|1].lazy +=lazy;
	tr[p].lazy = 0;
}
void build(int l,int r,int p)
{
	tr[p].l = l,tr[p].r = r;
	if(l==r)
	{
		tr[p].sum = a[l];
		return ;
	}
	int m = l+(r-l)/2;
	build(l,m,p<<1);
	build(m+1,r,p<<1|1);
	pushup(p);
}
//注意此处d用ll,线段树与值有关统一用ll
void update(int x,int y,int p,ll d)
{
	int l = tr[p].l, r = tr[p].r;
	if(x<=l&&r<=y) 
	{
		tr[p].sum += d*(r-l+1);
		tr[p].lazy+=d;
		return ;
	}
	pushdown(p);
	int m = l+(r-l)/2;
	if(x<=m) update(x,y,p<<1,d);
	if(y>m) update(x,y,p<<1|1,d);
	pushup(p);
}
ll query(int x,int y,int p)
{
	int l = tr[p].l, r = tr[p].r;
	if(x>r||l>y) return 0;
	if(x<=l&&r<=y) return tr[p].sum;
	pushdown(p);
	return query(x,y,p<<1)+query(x,y,p<<1|1);
}
void solve()
{
	cin>>n>>q;
	build(1,n,1);
	for(int i=1;i<=q;++i) 
	{
		int op;
		cin>>op;
		if(op&1) 
		{
			int l,r,d;
			cin>>l>>r>>d;
			update(l,r,1,d);
		}
		else
		{
			int x;
			cin>>x;
			cout<<query(1,x,1)<<'\n';
		}
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T=1;
	//cin>>T;
	while(T--)
	{
		solve();
	}
}

逆序对

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<string> VS;
typedef vector<int> VI;
typedef vector<vector<int>> VVI;
const int N = 100010;
int c[N];
int n;
inline int lowbit(int x) {return x&-x;}
inline void update(int x,int d) {while(x<=n) c[x]+=d,x+=lowbit(x);}
inline ll sum(int x) {ll res = 0;while(x) res+=c[x],x-=lowbit(x);return res;}

void solve()
{
	cin>>n;
	ll ans = 0;
	for(int i=1;i<=n;++i) 
	{
		int x;
		cin>>x;
		update(x,1);
		ans+=sum(n)-sum(x);
	}
	cout<<ans<<'\n';
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T=1;
	//cin>>T;
	while(T--)
	{
		solve();
	}
}

树状数组二分

题意:1.修改a[x] = d,2.输入s,求最大的T,使得i=1Tai<=s

解析:从高位向低位枚举,根据树状数组定义f(i) = i=ilowbit(i)+1iai 类似倍增当pos'变成pos时,t增加了c[pos'+1]~c[pos],最终确定位置T

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<string> VS;
typedef vector<int> VI;
typedef vector<vector<int>> VVI;
const int N = 2e5+10;
ll a[N],c[N];
int n,q;
inline int lowbit(int x) {return x&-x;}
inline void update(int x,int d) {while(x<=n) c[x]+=d,x+=lowbit(x);}
inline int log_2(ll x) {return 63-__builtin_clzll(x);}
inline int query(ll s)
{
	int x = log_2(n)+1; //此处的x应该为log2(n)的上取整
	ll t = 0, pos = 0;
	for(int j=x;j>=0;--j)
	{
		if(pos+(1<<j)<=n&&t+c[pos+(1<<j)]<=s)
		{
			t+=c[pos+(1<<j)];
			//注意应该先更新t后更新pos
			pos+=(1<<j);
		}
	}
	return pos;
}

void solve()
{
	cin>>n>>q;
	for(int i=1;i<=n;++i)
	{
		cin>>a[i];
		update(i,a[i]);
	}
	for(int i=1;i<=q;++i)
	{
		int op;
		cin>>op;
		if(op&1)
		{
			int x,d;
			cin>>x>>d;
			update(x,d-a[x]);//将修改变为增加
			a[x] = d;
		}
		else
		{
			ll s;
			cin>>s;
			cout<<query(s)<<'\n';
		}
	}
	
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T=1;
	//cin>>T;
	while(T--)
	{
		solve();
	}
}

二维数组数组

题意:1.修改a[x][y] = d 2.查询i=1xj=1yai,j

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<string> VS;
typedef vector<int> VI;
typedef vector<vector<int>> VVI;
const int N = 501;
ll a[N][N],c[N][N];
int n,m,q;
inline int lowbit(int x) {return x&-x;}
inline void update(int x,int y,int d)
{
	for(int p=x;p<=n;p+=lowbit(p))
	 for(int q=y;q<=m;q+=lowbit(q))
	 c[p][q]+=d;
}
inline ll query(int x,int y)
{
	//注意此处不能直接用x,y来减
	ll res = 0;
	for(int p=x;p;p-=lowbit(p))
	 for(int q=y;q;q-=lowbit(q))
	 res+=c[p][q];
	return res;
}

void solve()
{
	cin>>n>>m>>q;
	for(int i=1;i<=n;++i)
	 for(int j=1;j<=m;++j)
	 {
	 	cin>>a[i][j];
	 	update(i,j,a[i][j]);
	 }
	for(int i=1;i<=q;++i)
	{
		int op;
		cin>>op;
		if(op&1)
		{
			int x,y,d;
			cin>>x>>y>>d;
			update(x,y,d-a[x][y]);//将修改变为增加
			a[x][y] = d;
		}
		else
		{
			int x,y;
			cin>>x>>y;
			cout<<query(x,y)<<'\n';
		}
	}
	
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T=1;
	//cin>>T;
	while(T--)
	{
		solve();
	}
}

线段树

线段树1

题意:1.修改a[x] = d。2.查询l~r的最小值及其出现次数

给出封装化线段树

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<string> VS;
typedef vector<int> VI;
typedef vector<vector<int>> VVI;
const int N = 2e5+10;
int n,q;
int a[N];
struct info
{
	int minn,mincnt;
};
info operator + (const info& l,const info& r)
{
	info a;
	a.minn = min(l.minn,r.minn);
	if(l.minn==r.minn) a.mincnt = l.mincnt+r.mincnt;
	else if(l.minn<r.minn) a.mincnt = l.mincnt;
	else a.mincnt = r.mincnt;
	return a;
}
struct node
{
	int l,r;
	info val;
}tr[N<<2];
void pushup(int p)
{
	tr[p].val = tr[p<<1].val + tr[p<<1|1].val;
}
void build(int l,int r,int p)
{
	tr[p].l = l,tr[p].r = r;
	if(l==r)
	{
		tr[p].val = {a[l],1};
		return ;
	}
	int m = (l+r)/2;
	build(l,m,p<<1);
	build(m+1,r,p<<1|1);
	pushup(p);
}
void update(int x,int d,int p)
{
	int l = tr[p].l, r = tr[p].r;
	if(l==r)
	{
		tr[p].val = {d,1};
		return ;
	}
	int m = (l+r)/2;
	if(x<=m) update(x,d,p<<1);
	else update(x,d,p<<1|1);
	pushup(p);
}
info query(int x,int y,int p)
{
	int l = tr[p].l,r = tr[p].r;
	if(x==l&&r==y) return tr[p].val;
	info ans;
	int m = (l+r)/2;
	if(y<=m) return query(x,y,p<<1);
	else if(x>m) return query(x,y,p<<1|1);
	else return query(x,m,p<<1)+query(m+1,y,p<<1|1);
}
void solve()
{
	cin>>n>>q;
	for(int i=1;i<=n;++i) cin>>a[i];
	build(1,n,1);
	//cout<<query(1,n,1).minn<<' '<<query(1,n,1).mincnt<<'\n';
	for(int i=1;i<=q;++i)
	{
		int op;
		cin>>op;
		if(op&1)
		{
			int x,d;
			cin>>x>>d;
			update(x,d,1);
		}
		else
		{
			int x,y;
			cin>>x>>y;
			auto res = query(x,y,1);
			cout<<res.minn<<" "<<res.mincnt<<'\n';
		}
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T=1;
	//cin>>T;
	while(T--)
	{
		solve();
	}
}

线段树2

题意:1.修改a[x] = d 2.查询[l,r]的最大子段和

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<string> VS;
typedef vector<int> VI;
typedef vector<vector<int>> VVI;
const int N = 2e5+10;
int n,q;
int a[N];
struct info
{
	ll mms,mpre,msuf,sum;
	info() {}
	info(int a) :mms(a),mpre(a),msuf(a),sum(a) {}
};
info operator + (const info& l,const info& r)
{
	info a;
	a.mms = max({l.mms,r.mms,l.msuf+r.mpre});
	a.mpre = max(l.mpre,l.sum+r.mpre);
	a.msuf = max(r.msuf,l.msuf+r.sum);
	a.sum = l.sum + r.sum;
	return a;
}
struct node
{
	int l,r;
	info val;
}tr[N<<2];
void pushup(int p)
{
	tr[p].val = tr[p<<1].val + tr[p<<1|1].val;
}
void build(int l,int r,int p)
{
	tr[p].l = l,tr[p].r = r;
	if(l==r)
	{
		tr[p].val = info(a[l]);
		return ;
	}
	int m = (l+r)/2;
	build(l,m,p<<1);
	build(m+1,r,p<<1|1);
	pushup(p);
}
//单点修改
void update(int x,int d,int p)
{
	int l = tr[p].l, r = tr[p].r;
	if(l==r)
	{
		tr[p].val = info(d);
		return ;
	}
	int m = (l+r)/2;
	if(x<=m) update(x,d,p<<1);
	else update(x,d,p<<1|1);
	pushup(p);
}
info query(int x,int y,int p)
{
	int l = tr[p].l,r = tr[p].r;
	if(x==l&&r==y) return tr[p].val;
	info ans;
	int m = (l+r)/2;
	if(y<=m) return query(x,y,p<<1);
	else if(x>m) return query(x,y,p<<1|1);
	else return query(x,m,p<<1)+query(m+1,y,p<<1|1);
}
void solve()
{
	cin>>n>>q;
	for(int i=1;i<=n;++i) cin>>a[i];
	build(1,n,1);
	//cout<<query(1,n,1).minn<<' '<<query(1,n,1).mincnt<<'\n';
	for(int i=1;i<=q;++i)
	{
		int op;
		cin>>op;
		if(op&1)
		{
			int x,d;
			cin>>x>>d;
			update(x,d,1);
		}
		else
		{
			int x,y;
			cin>>x>>y;
			auto res = query(x,y,1);
			cout<<res.mms<<'\n';
		}
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T=1;
	//cin>>T;
	while(T--)
	{
		solve();
	}
}

线段树打标记1

题意:1.将[l,r]加上d 2.求[l,r]的max

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<string> VS;
typedef vector<int> VI;
typedef vector<vector<int>> VVI;
const int N = 2e5+10;
int n,q;
int a[N];
struct info
{
	ll maxn;
	info() {}
	info(int a) :maxn(a) {}
};
struct tag
{
	ll add;
};
//用于合并左右子树
info operator + (const info& l,const info& r)
{
	info a;
	a.maxn = max(l.maxn , r.maxn);
	return a;
}
//settag时需要处理val和tag的关系
info operator + (const info &v,const tag& t)
{
	info a;
	a.maxn = v.maxn + t.add;
	return a;
}
//下推标记时标记的合并
tag operator + (const tag &t1,const tag&t2)
{
	tag t;
	t.add = t1.add + t2.add;
	return t;
}
struct node
{
	int l,r;
	info val;
	tag t;
}tr[N<<2];
void settag(int p,tag t)
{
	tr[p].val = tr[p].val + t;
	tr[p].t = tr[p].t + t;
}
void pushup(int p)
{
	tr[p].val = tr[p<<1].val + tr[p<<1|1].val;
}
void pushdown(int p)
{
	if(tr[p].t.add!=0) //标记非空
	{
	settag(p<<1,tr[p].t);
	settag(p<<1|1,tr[p].t);
	tr[p].t.add = 0; //下推完记得清空
	}
}
void build(int l,int r,int p)
{
	tr[p].l = l,tr[p].r = r;
	if(l==r)
	{
		tr[p].val = info(a[l]);
		return ;
	}
	int m = (l+r)/2;
	build(l,m,p<<1);
	build(m+1,r,p<<1|1);
	pushup(p);
}
//区间修改
void update(int x,int y,tag C,int p)
{
	int l = tr[p].l, r = tr[p].r;
	if(l==x&&r==y) 
	{
		settag(p,C);
		return ;
	}
	pushdown(p);
	int m = (l+r)/2;
	if(y<=m) update(x,y,C,p<<1);
	else if(x>m) update(x,y,C,p<<1|1);
	else 
	{
		update(x,m,C,p<<1);
		update(m+1,y,C,p<<1|1);
	}
	pushup(p);
}
info query(int x,int y,int p)
{
	int l = tr[p].l,r = tr[p].r;
	if(x==l&&r==y) return tr[p].val;
	pushdown(p);
	info ans;
	int m = (l+r)/2;
	if(y<=m) return query(x,y,p<<1);
	else if(x>m) return query(x,y,p<<1|1);
	else return query(x,m,p<<1)+query(m+1,y,p<<1|1);
}
void solve()
{
	cin>>n>>q;
	for(int i=1;i<=n;++i) cin>>a[i];
	build(1,n,1);
	//cout<<query(1,n,1).minn<<' '<<query(1,n,1).mincnt<<'\n';
	for(int i=1;i<=q;++i)
	{
		int op;
		cin>>op;
		if(op&1)
		{
			int x,y,C;
			cin>>x>>y>>C;
			update(x,y,(tag){C},1);
		}
		else
		{
			int x,y;
			cin>>x>>y;
			auto res = query(x,y,1);
			cout<<res.maxn<<'\n';
		}
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T=1;
	//cin>>T;
	while(T--)
	{
		solve();
	}
}

线段树打标记2

题意:1.[l,r]加d 2.[l,r]乘d 3.[l,r]等于d 4.查询[l,r]

非结构化写法

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<string> VS;
typedef vector<int> VI;
typedef vector<vector<int>> VVI;
const int N = 2e5+10;
int n,q;
int a[N];
const int mod = 1e9+7;
//一般要修改tag的构造函数
struct tag
{
	ll mul,add;
	tag():mul(1),add(0) {}
	tag(ll a,ll b):mul(a),add(b) {}
};
//下推标记时标记的合并,一定要注意
tag operator + (const tag &t1,const tag&t2)
{
	tag t;
	t.mul = t1.mul*t2.mul%mod;
	t.add = (t1.mul*t2.add%mod + t1.add)%mod;
	return t;
}
//非封装化写法(当info内需要保留的内容少时)
struct node
{
	int l,r;
	ll sum,sz;
	tag t;
}tr[N<<2];
//settag里包裹tag对当前节点的影响(需要修改)和tag的下推(无需修改)
void settag(int p,tag t)
{	
	//注意此处的顺序要用新的tag更新原本的tag
	tr[p].sum = (t.mul*tr[p].sum%mod + t.add*tr[p].sz%mod)%mod;
	tr[p].t = t + tr[p].t;
}
//注意合并写法
void pushup(int p)
{
	tr[p].sum = (tr[p<<1].sum + tr[p<<1|1].sum)%mod;
}
//注意修改标记
void pushdown(int p)
{
	if(tr[p].t.mul!=1||tr[p].t.add!=0) //标记非空
	{
	settag(p<<1,tr[p].t);
	settag(p<<1|1,tr[p].t);
	tr[p].t.mul = 1;
	tr[p].t.add = 0; //下推完记得清空
	}
}
//一般不用修改
void build(int l,int r,int p)
{
	tr[p].l = l,tr[p].r = r,tr[p].sz = r-l+1;
	if(l==r)
	{
		tr[p].sum = a[l];
		return ;
	}
	int m = (l+r)/2;
	build(l,m,p<<1);
	build(m+1,r,p<<1|1);
	pushup(p);
}
//一般不用修改
void update(int x,int y,tag C,int p)
{
	int l = tr[p].l, r = tr[p].r;
	if(l==x&&r==y) 
	{
		settag(p,C);
		return ;
	}
	pushdown(p);
	
	int m = (l+r)/2;
	if(y<=m) update(x,y,C,p<<1);
	else if(x>m) update(x,y,C,p<<1|1);
	else 
	{
		update(x,m,C,p<<1);
		update(m+1,y,C,p<<1|1);
	}
	pushup(p);
}
//一般不用修改
ll query(int x,int y,int p)
{
	int l = tr[p].l,r = tr[p].r;
	if(x==l&&r==y) return tr[p].sum;
	pushdown(p);
	
	int m = (l+r)/2;
	if(y<=m) return query(x,y,p<<1);
	else if(x>m) return query(x,y,p<<1|1);
	else return (query(x,m,p<<1)+query(m+1,y,p<<1|1))%mod;
}
void solve()
{
	cin>>n>>q;
	for(int i=1;i<=n;++i) cin>>a[i];
	build(1,n,1);
	for(int i=1;i<=q;++i)
	{
		int op;
		cin>>op;
		if(op==1)
		{
			int x,y,d;
			cin>>x>>y>>d;
			update(x,y,tag(1,d),1);
		}
		else if(op==2)
		{
			int x,y,d;
			cin>>x>>y>>d;
			update(x,y,tag(d,0),1);
		}
		else if(op==3)
		{
			int x,y,d;
			cin>>x>>y>>d;
			update(x,y,tag(0,d),1);
		}
		else
		{
			int x,y;
			cin>>x>>y;
			ll res = query(x,y,1);
			cout<<res<<'\n';
		}
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T=1;
	//cin>>T;
	while(T--)
	{
		solve();
	}
}

结构化写法

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<string> VS;
typedef vector<int> VI;
typedef vector<vector<int>> VVI;
const int N = 2e5+10;
int n,q;
int a[N];
const int mod = 1e9+7;
//一般要修改info和tag的构造函数
struct info
{
	ll sum,sz;
	info() {}
	info(ll a) :sum(a),sz(1) {}
};
struct tag
{
	ll mul,add;
	tag():mul(1),add(0) {}
	tag(ll a,ll b):mul(a),add(b) {}
};
//线段树如果wa掉一定先检查这三个重载
//用于合并左右子树
info operator + (const info& l,const info& r)
{
	info a;
	a.sum = (l.sum+r.sum)%mod;
	a.sz = l.sz+r.sz;
	return a;
}
//settag时需要处理val和tag的关系
//本题中由于更新sum需要区间长度,故info中添加了sz
//注意此处由于是返回一个info,同时要保留sz信息
info operator + (const info &v,const tag& t)
{
	info a;
	a.sum = (v.sum*t.mul%mod + t.add*v.sz%mod)%mod;
	a.sz = v.sz;
	return a;
}
//下推标记时标记的合并
tag operator + (const tag &t1,const tag&t2)
{
	tag t;
	t.mul = t1.mul*t2.mul%mod;
	t.add = (t1.mul*t2.add%mod + t1.add)%mod;
	return t;
}
struct node
{
	int l,r;
	info val;
	tag t;
}tr[N<<2];
//一般不用修改,但要注意tag更新顺序
void settag(int p,tag t)
{
	tr[p].val = tr[p].val + t;
	//注意此处的顺序要用新的tag更新原本的tag
	tr[p].t = t + tr[p].t;
}
//一般不用修改
void pushup(int p)
{
	tr[p].val = tr[p<<1].val + tr[p<<1|1].val;
}
//一般需要修改标记非空的判定条件
void pushdown(int p)
{
	if(tr[p].t.add!=0||tr[p].t.mul!=1) //标记非空
	{
	settag(p<<1,tr[p].t);
	settag(p<<1|1,tr[p].t);
	tr[p].t.add = 0; //下推完记得清空
	tr[p].t.mul = 1;
	}
}
//一般不用修改
void build(int l,int r,int p)
{
	tr[p].l = l,tr[p].r = r;
	if(l==r)
	{
		tr[p].val = info(a[l]);
		return ;
	}
	int m = (l+r)/2;
	build(l,m,p<<1);
	build(m+1,r,p<<1|1);
	pushup(p);
}
//一般不用修改
void update(int x,int y,tag C,int p)
{
	int l = tr[p].l, r = tr[p].r;
	if(l==x&&r==y) 
	{
		settag(p,C);
		return ;
	}
	pushdown(p);
	
	int m = (l+r)/2;
	if(y<=m) update(x,y,C,p<<1);
	else if(x>m) update(x,y,C,p<<1|1);
	else 
	{
		update(x,m,C,p<<1);
		update(m+1,y,C,p<<1|1);
	}
	pushup(p);
}
//一般不用修改
info query(int x,int y,int p)
{
	int l = tr[p].l,r = tr[p].r;
	if(x==l&&r==y) return tr[p].val;
	pushdown(p);
	info ans;
	int m = (l+r)/2;
	if(y<=m) return query(x,y,p<<1);
	else if(x>m) return query(x,y,p<<1|1);
	else return query(x,m,p<<1)+query(m+1,y,p<<1|1);
}
void solve()
{
	cin>>n>>q;
	for(int i=1;i<=n;++i) cin>>a[i];
	build(1,n,1);
	for(int i=1;i<=q;++i)
	{
		int op;
		cin>>op;
		if(op==1)
		{
			int x,y,d;
			cin>>x>>y>>d;
			update(x,y,tag(1,d),1);
		}
		else if(op==2)
		{
			int x,y,d;
			cin>>x>>y>>d;
			update(x,y,tag(d,0),1);
		}
		else if(op==3)
		{
			int x,y,d;
			cin>>x>>y>>d;
			update(x,y,tag(0,d),1);
		}
		else
		{
			int x,y;
			cin>>x>>y;
			auto res = query(x,y,1);
			cout<<res.sum<<'\n';
		}
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T=1;
	//cin>>T;
	while(T--)
	{
		solve();
	}
}

线段树上二分

题意:1.a[x] = d 2.求[l,r]第一个>=d的下标

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<string> VS;
typedef vector<int> VI;
typedef vector<vector<int>> VVI;
const int N = 2e5+10;
int n,q;
int a[N];
struct info
{
	int maxn;
};
info operator + (const info& l,const info& r)
{
	info a;
	a.maxn = max(l.maxn,r.maxn);
	return a;
}
struct node
{
	int l,r;
	info val;
}tr[N<<2];
void pushup(int p)
{
	tr[p].val = tr[p<<1].val + tr[p<<1|1].val;
}
void build(int l,int r,int p)
{
	tr[p].l = l,tr[p].r = r;
	if(l==r)
	{
		tr[p].val = {a[l]};
		return ;
	}
	int m = (l+r)/2;
	build(l,m,p<<1);
	build(m+1,r,p<<1|1);
	pushup(p);
}
void update(int x,int d,int p)
{
	int l = tr[p].l, r = tr[p].r;
	if(l==r)
	{
		tr[p].val = {d};
		return ;
	}
	int m = (l+r)/2;
	if(x<=m) update(x,d,p<<1);
	else update(x,d,p<<1|1);
	pushup(p);
}
int search(int x,int y,int d,int p)
{
	int l = tr[p].l,r = tr[p].r;
	int m = (l+r)/2;
	if(l==x&&y==r)
	{
		if(tr[p].val.maxn < d) return -1;
		if(l==r) return l;
		if(tr[p<<1].val.maxn>=d) return search(x,m,d,p<<1);
		else return search(m+1,y,d,p<<1|1);
	}
	if(y<=m) return search(x,y,d,p<<1);
	else if(x>m) return search(x,y,d,p<<1|1);
	else
	{
		int tmp = search(x,m,d,p<<1);
		if(tmp!=-1) return tmp;
		else return search(m+1,y,d,p<<1|1);
	}
}
void solve()
{
	cin>>n>>q;
	for(int i=1;i<=n;++i) cin>>a[i];
	build(1,n,1);
	
	for(int i=1;i<=q;++i)
	{
		int op;
		cin>>op;
		if(op&1)
		{
			int x,d;
			cin>>x>>d;
			update(x,d,1);
		}
		else
		{
			int x,y,d;
			cin>>x>>y>>d;
			int res = search(x,y,d,1);
			cout<<res<<'\n';
		}
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T=1;
	//cin>>T;
	while(T--)
	{
		solve();
	}
}

扫描线

二维数点

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<string> VS;
typedef vector<int> VI;
typedef vector<vector<int>> VVI;
//以y轴扫描,y为第一关键字,操作为第二关键字
vector<array<int,4>> event;
vector<int> vx;// 用于离散化
const int N = 2e5+10;
int m;
int c[N*2],ans[N];
inline int lowbit(int x) {return x&-x;}
inline void update(int x,int y) {while(x<=m) c[x]+=y,x+=lowbit(x);}
inline ll sum(int x) {ll res=0;while(x) res+=c[x],x-=lowbit(x);return res;}
int mp(int x)
{
    //注意可能查询时存在x不在vx里的,若用lower_bound可能会有下标冲突
    return upper_bound(vx.begin(),vx.end(),x) - vx.begin() ;
};
void solve()
{
    int n,q;
    cin>>n>>q;
    for(int i=0;i<n;++i)
    {
        int x,y;
        cin>>x>>y;
        vx.push_back(x);
        event.push_back({y,0,x});
    }
    //二维数点 +x2y2 + x1-1,y1-1,-x1-1,y2,-x2,y1-1
    for(int i=1;i<=q;++i)
    {
        int x1,x2,y1,y2;
        cin>>x1>>x2>>y1>>y2;
        //需要保存标号
        //2为加,1为减
        event.push_back({y2,2,x2,i});
        event.push_back({y1-1,2,x1-1,i});
        event.push_back({y2,1,x1-1,i});
        event.push_back({y1-1,1,x2,i});
    }
    sort(event.begin(),event.end());
    sort(vx.begin(),vx.end());
    vx.erase(unique(vx.begin(),vx.end()),vx.end());
    m = vx.size();
    for(auto evt:event)
    {
    	int pos = mp(evt[2]);
    	if(!evt[1]) {update(pos,1);}
    	else
    	{
    		//对于一个event来说,sum(pos)相当于存了一个(evt[0],pos)的矩阵
    		int tmp = sum(pos);
    		if(evt[1]==1) ans[evt[3]]-=tmp;
    		else ans[evt[3]]+=tmp;
    	}
    }
    for(int i=1;i<=n;++i) cout<<ans[i]<<'\n';
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int T=1;
    //cin>>T;
    while(T--)
    {
        solve();
    }
}

区间不同数之和

思路一:转化为二维数点,pre[i]表示i上一次出现的位置,查找的点为L<= i <= R, pre[i] < L 的点值和

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<string> VS;
typedef vector<int> VI;
typedef vector<vector<int>> VVI;
//以y轴扫描,y为第一关键字,操作为第二关键字
vector<array<int,4>> event;
vector<int> vx;// 用于离散化
const int N = 2e5+10;
int m;
ll c[N*2],ans[N];
inline int lowbit(int x) {return x&-x;}
inline void update(int x,int y) {while(x<=m) c[x]+=y,x+=lowbit(x);}
inline ll sum(int x) {ll res=0;while(x) res+=c[x],x-=lowbit(x);return res;}
int last[N],pre[N];
//转化到二维数点,求i在[l,r],pre[i]<l的点的个数
void solve()
{
    int n,q;
    cin>>n>>q;
    memset(last,-1,sizeof last);
    for(int i=1;i<=n;++i)
    {
        int a;
        cin>>a;
        pre[i] = last[a];
        last[a] = i;
        //x为下标,y为pre[i],此时evt[3]里存的是数值
        event.push_back({pre[i],0,i,a});
    }
    //二维数点 +x2y2 + x1-1,y1-1,-x1-1,y2,-x2,y1-1
    for(int i=1;i<=q;++i)
    {
        int l,r;
        cin>>l>>r;
        //需要保存标号
        //2为加,1为减
        event.push_back({l-1,2,r,i});
        event.push_back({l-1,1,l-1,i});
    }
    sort(event.begin(),event.end());
    m = n;
    for(auto evt:event)
    {
    	int pos = evt[2];
    	//此时更新应该+a
    	if(!evt[1]) update(pos,evt[3]);
    	else
    	{
    		//对于一个event来说,sum(pos)相当于存了一个(evt[0],pos)的矩阵
    		ll tmp = sum(pos);
    		if(evt[1]==1) ans[evt[3]]-=tmp;
    		else ans[evt[3]]+=tmp;
    	}
    }
    for(int i=1;i<=n;++i) cout<<ans[i]<<'\n';
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int T=1;
    //cin>>T;
    while(T--)
    {
        solve();
    }
}

思路二 : 枚举r,维护ans[l]表示[l,r]的答案,当r从r-1转移到r时,只有pos[r]<i 的区间需要增加

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<string> VS;
typedef vector<int> VI;
typedef vector<vector<int>> VVI;
const int N = 2e5+10;
int m;
//注意由于扫描时可能树状数组的区间和题目中的n不一样,需要考虑后赋值
ll c[N*2],ans[N];
inline int lowbit(int x) {return x&-x;}
inline void update(int x,int y) {while(x<=m) c[x]+=y,x+=lowbit(x);}
inline ll sum(int x) {ll res=0;while(x) res+=c[x],x-=lowbit(x);return res;}
//pre[r]表示上次一a[r]出现的位置
int last[N],pre[N],a[N];
vector<PII> qu[N]; 
void solve()
{
    int n,q;
    cin>>n>>q;
    for(int i=1;i<=n;++i)
    {
        cin>>a[i];
        pre[i] = last[a[i]];
        last[a[i]] = i;
    }
    for(int i=1;i<=q;++i)
    {
        int l,r;
        cin>>l>>r;
        qu[r].push_back({l,i});
    }
    //以r从1~n,对每一个r,ans[i]表示i~r的答案
    //可以发现当r从r-1向r更新时,只有pos[r]<i的区间会被更新
    m = n;
    for(int r=1;r<=n;++r)
    {
    	int p = pre[r];
    	update(p+1,a[r]);
    	update(r+1,-a[r]);
    	for(auto t:qu[r])
    	{
    		ans[t.y] = sum(t.x);
    	}
    }
    for(int i=1;i<=q;++i) cout<<ans[i]<<'\n';
    
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int T=1;
    //cin>>T;
    while(T--)
    {
        solve();
    }
}

矩形面积并

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<string> VS;
typedef vector<int> VI;
typedef vector<vector<int>> VVI;
//以y轴扫描,y为第一关键字,操作为第二关键字
vector<array<int,4>> event;
const int N = 2e5+10;
vector<int> vx;
//由于左右端点线段树需要开八倍
struct info
{
	int minn,mincnt;
	info() {}
	info(int a,int x) :minn(a), mincnt(x) {}
};
struct tag
{
	ll add;
};
info operator + (const info& l,const info& r)
{
	info a;
	a.minn = min(l.minn,r.minn);
	if(l.minn==r.minn) a.mincnt = l.mincnt+r.mincnt;
	else if(l.minn<r.minn) a.mincnt = l.mincnt;
	else a.mincnt = r.mincnt;
	return a;
}
info operator + (const info &v,const tag& t)
{
	info a = v;
	a.minn = v.minn + t.add;
	return a;
}
//下推标记时标记的合并
tag operator + (const tag &t1,const tag&t2)
{
	tag t;
	t.add = t1.add + t2.add;
	return t;
}
struct node
{
	int l,r;
	info val;
	tag t;
}tr[N<<4];
void settag(int p,tag t)
{
	tr[p].val = tr[p].val + t;
	tr[p].t = tr[p].t + t;
}
void pushup(int p)
{
	tr[p].val = tr[p<<1].val + tr[p<<1|1].val;
}
void pushdown(int p)
{
	if(tr[p].t.add!=0) //标记非空
	{
	settag(p<<1,tr[p].t);
	settag(p<<1|1,tr[p].t);
	tr[p].t.add = 0; //下推完记得清空
	}
}
void build(int l,int r,int p)
{
	tr[p].l = l,tr[p].r = r;
	if(l==r)
	{
		tr[p].val = info(0,vx[l]-vx[l-1]);
		return ;
	}
	int m = (l+r)/2;
	build(l,m,p<<1);
	build(m+1,r,p<<1|1);
	pushup(p);
}
//区间修改
void update(int x,int y,tag C,int p)
{
	int l = tr[p].l, r = tr[p].r;
	if(l==x&&r==y) 
	{
		settag(p,C);
		return ;
	}
	pushdown(p);
	int m = (l+r)/2;
	if(y<=m) update(x,y,C,p<<1);
	else if(x>m) update(x,y,C,p<<1|1);
	else 
	{
		update(x,m,C,p<<1);
		update(m+1,y,C,p<<1|1);
	}
	pushup(p);
}
int m;
//vector<int> vx;
void divide()
{
	sort(vx.begin(),vx.end());
	vx.erase(unique(vx.begin(),vx.end()),vx.end());
}
int mp(int x)
{
	return upper_bound(vx.begin(),vx.end(),x)-vx.begin();
}
void solve()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;++i)
    {
        int x1,x2,y1,y2;
        cin>>x1>>x2>>y1>>y2;
        vx.push_back(x1);
        vx.push_back(x2);
        event.push_back({y1,1,x1,x2});
        event.push_back({y2,-1,x1,x2});
    }
    divide();
    //化点为段,数量减一
    m = vx.size()-1;
    build(1,m,1);
    //注意扫描之前必须先排序
    sort(event.begin(),event.end());
    int totlen = tr[1].val.mincnt;
    ll ans = 0;
    int prey = 0;
    for(auto evt:event)
    {
    	int cov = totlen;
    	//由于给的是点坐标而有效面积是区间所以x2需要减一
    	int x1 = mp(evt[2]),x2 = mp(evt[3])-1;
    	//注意若线段树的左区间大于右区间会RE
    	if(x1>x2) continue;
    	if(!tr[1].val.minn)
    	{
    		cov -= tr[1].val.mincnt;
    	}
    	//先统计后修改
    	ans += (ll)cov*(evt[0]-prey);
    	//注意prey的更新
    	prey = evt[0];
    	update(x1,x2,(tag){evt[1]},1);
    }
    cout<<ans<<'\n';
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int T=1;
    //cin>>T;
    while(T--)
    {
        solve();
    }
}

权值线段树(特值bit建树)

异或第k小

题意 :给定n个数,q次询问,每次给x,k,查询a[i]^x的第k小

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<string> VS;
typedef vector<int> VI;
typedef vector<vector<int>> VVI;
const int N = 2e5+10,M = 31;
struct node
{
	int s[2];
	int sz;
}tr[N*32];

void solve()
{
	int root = 0,tot = 0;
	root = ++tot;
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;++i)
	{
		int x;
		cin>>x;
		int p = root;
		for(int j=M;j>=0;--j)
		{
			tr[p].sz++;
			int w = (x>>j)&1;
			if(!tr[p].s[w]) tr[p].s[w] = ++tot;
			p = tr[p].s[w];
		}
		tr[p].sz++;
	}
	
	for(int i=0;i<m;++i)
	{
		int x,k;
		cin>>x>>k;
		int p = root;
		int ans = 0;
		for(int j=M;j>=0;--j)
		{
			int w = (x>>j)&1;
			//和x当前位一样的数大于等于k,ans当前位为0
			//注意查找当前的sz需要再套一个tr
			if(tr[tr[p].s[w]].sz>=k)
			{
				p = tr[p].s[w];
			}
			else
			{
				k -= tr[tr[p].s[w]].sz;
				ans ^= (1<<j);
				p = tr[p].s[w^1];
			}
		}
		cout<<ans<<'\n';
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T=1;
	//cin>>T;
	while(T--)
	{
		solve();
	}
}

mex

求[l,r]的mex值

枚举r,更新pos[a[r]],线段树上二分寻找下标最小的x使得pos[x]<l

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<string> VS;
typedef vector<int> VI;
typedef vector<vector<int>> VVI;
const int N = 2e5+10;
int n,q;
int a[N];
struct info
{
	int minn;
};
info operator + (const info& l,const info& r)
{
	info a;
	a.minn = min(l.minn,r.minn);
	return a;
}
struct node
{
	int l,r;
	info val;
}tr[N<<2];
void pushup(int p)
{
	tr[p].val = tr[p<<1].val + tr[p<<1|1].val;
}
void build(int l,int r,int p)
{
	tr[p].l = l,tr[p].r = r;
	if(l==r)
	{
		tr[p].val = {0};
		return ;
	}
	int m = (l+r)/2;
	build(l,m,p<<1);
	build(m+1,r,p<<1|1);
	pushup(p);
}
void update(int x,int d,int p)
{
	int l = tr[p].l, r = tr[p].r;
	if(l==r)
	{
		tr[p].val = {d};
		return ;
	}
	int m = (l+r)/2;
	if(x<=m) update(x,d,p<<1);
	else update(x,d,p<<1|1);
	pushup(p);
}
int search(int x,int y,int d,int p)
{
	int l = tr[p].l , r = tr[p].r;
	int m = (l+r)/2;
	if(tr[p].val.minn > d) return -1;
	if(l==r) return l;
	if(tr[p<<1].val.minn < d) return search(x,m,d,p<<1);
	else return search(m+1,y,d,p<<1|1);
}
vector<PII> qu[N];
int ans[N],pos[N];
void solve()
{
	cin>>n>>q;
	for(int i=1;i<=n;++i) 
	{
		cin>>a[i];
		a[i] = min(a[i],n+1);
	}
	build(1,n,1);
	for(int i=1;i<=q;++i)
	{
		int l,r;
		cin>>l>>r;
		qu[r].push_back({l,i});
	}
	for(int r=1;r<=n;++r)
	{
		//更新a[r]+1处的值为r(将0转化到1)
		update(a[r]+1,r,1);
		//查找最小的x使得pos[x]<l
		for(auto [l,i]:qu[r]) ans[i] = search(1,n,l,1);
	}
	for(int i=1;i<=q;++i) cout<<ans[i]-1<<'\n';
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T=1;
	//cin>>T;
	while(T--)
	{
		solve();
	}
}

笛卡尔树

题意:给定一个 1∼n的排列p,构造一个 1∼n的排列 q,满足对于任意区间 l,r,满足 pl,pl+1,…,pr中的最小值的位置,和ql,ql+1,…,qr的最小值的位置相同。输出满足条件的字典序最小的 q

题解:所有区间的最小值位置一样即两个排列的笛卡尔树一样,由于要字典序最小,所以再p的笛卡尔树上先序遍历赋值构造即可

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<string> VS;
typedef vector<int> VI;
typedef vector<vector<int>> VVI;
const int N = 1e6+10;
int a[N],l[N],r[N],ans[N];
int tot,n;
void dfs(int u)
{
	ans[u] = ++tot;
	if(l[u]) dfs(l[u]);
	if(r[u]) dfs(r[u]);
}
void build()
{
	int root = 0;
	stack<int> stk;
	for(int i=1;i<=n;++i)
	{
		int last = 0;
		while(stk.size()&&a[i]<a[stk.top()]) 
		{
			last = stk.top();
			stk.pop();
		}
		if(!stk.empty()) r[stk.top()] = i;
		else root = i;
		l[i] = last;
		stk.push(i);
	}
	//root保存了根节点的编号
	dfs(root);
}
void solve()
{
	cin>>n;
	for(int i=1;i<=n;++i) cin>>a[i];
	build();
	for(int i=1;i<=n;++i) cout<<ans[i]<<" \n"[i==n];
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T=1;
	//cin>>T;
	while(T--)
	{
		solve();
	}
}

st表

RMQ

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int uint;
typedef vector<string> VS;
typedef vector<int> VI;
typedef vector<vector<int>> VVI;
unsigned int A, B, C;
inline unsigned int rng61() {
    A ^= A << 16;
    A ^= A >> 5;
    A ^= A << 1;
    unsigned int t = A;
    A = B;
    B = C;
    C ^= t ^ A;
    return C;
}
const int N = 1e6+10;
int n,q;
uint a[N],st[22][N];

void init_st()
{
	for(int i=1;i<=n;++i) st[0][i] = a[i];
	//注意先枚举的是j
	for(int j=1;j<=20;++j)
	  for(int i=1;i+(1<<(j-1))-1<=n;++i)
	  {
		  st[j][i] = max(st[j-1][i] , st[j-1][i+(1<<(j-1))]);
	  }
}

uint RMQ(int l,int r)
{
	//使用库函数__lg会比数组更快
	int len = __lg(r-l+1);
	return max(st[len][l],st[len][r-(1<<len)+1]);
}
void solve()
{
    cin>>n>>q>>A>>B>>C;
    uint ans = 0;
    for (int i = 1; i <= n; i++) {
        a[i] = rng61();
    }
    init_st();
    for (int i = 1; i <= q; i++) {
        unsigned int l = rng61() % n + 1, r = rng61() % n + 1;
        if (l > r) swap(l, r);
        int tmp = RMQ(l,r);
        ans^=tmp;
    }
    cout<<ans<<'\n';
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T=1;
	//cin>>T;
	while(T--)
	{
		solve();
	}
}

带权并查集

题意:n个数 操作1: 给定线索a[l]a[r]=x 2: 回答a[l]-a[r],题目通过2的答案修改t,强制在线

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int uint;
typedef vector<string> VS;
typedef vector<int> VI;
typedef vector<vector<int>> VVI;
const int N = 2e5+10;
ll w[N];//用w[i]表示a[i]-a[p];
int f[N];
int find(int x)
{
	if(f[x]==x) return x;
	int p = f[x];
	int q = find(f[x]);
	//路径压缩的时候维护w[i]
	//w[x] = a[x] - a[p];
	//w[p] = a[p] - a[rt]
	w[x] = w[x] + w[p];
	//注意路径压缩的返回值需要修改f[x]
	return f[x] = q;
}
void solve()
{
	int n,q;
	cin>>n>>q;
	for(int i=1;i<=n;++i) f[i] = i;
	ll t = 0;
	for(int i=0;i<q;++i)
	{
		int op;
		cin>>op;
        ll l,r,x;
		if(op&1)
		{
			cin>>l>>r>>x;
			//线索a[l]-a[r] = x;
			l = (l+t)%n+1, r = (r+t)%n+1;
			int pl = find(l), pr = find(r);
			if(pl == pr) continue;//如果之前就在一个并查集无需操作
			f[pl] = pr; //将l所在并查集合并到r
			// w[pl] = a[pl] - a[pr]
			// w[l] = a[l] - a[pl]
			// w[r] = a[r] - a[pr]
			//合并时要用w[l],w[r]维护w[pl]
			// w[l] - w[r] = a[l] - a[r] -w[pl] = x - w[pl]
			// w[pl] = x + w[r] - w[l]
			w[pl] = x + w[r] - w[l];
		}
		else
		{
			cin>>l>>r;
			l = (l+t)%n+1, r = (r+t)%n+1;
			int pl = find(l), pr = find(r);
			if(pl != pr) continue;
			ll res = w[l] - w[r];
			cout<<res<<'\n';
			t = abs(res);
		}
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T=1;
	//cin>>T;
	while(T--)
	{
		solve();
	}
}

根号分治

经典分治(取模数,普通图)

等差数列加

题意:初始值为0长度为n的数组a 操作1.令a[i%x==y] += d 2.求a[x]

题解: 对于 d>n 直接操作不会超过n次,对 d<n 的数用f[i][j]保存mod i == j的加和

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int uint;
typedef vector<string> VS;
typedef vector<int> VI;
typedef vector<vector<int>> VVI;
const int B = 500;
//f[i][j]表示的是%i后下标为j的数要增加的数
ll f[B+1][B];
void solve()
{
	int B = 500;
	int n,q;
	cin>>n>>q;
	vector<ll> a(n+1);
	
	while(q--)
	{
		int op;
		cin>>op;
		//cout<<op<<'\n';
		if(op&1)
		{
			int x,y,d;
			cin>>x>>y>>d;
			if(x<=B) f[x][y] += d;
			else
			{
				for(int i=y;i<=n;i+=x) a[i] += d;
			}
		}
		else
		{
			int x;
			cin>>x;
			ll tmp = 0;//注意此处不能修改a[x]的值
			for(int i=1;i<=B;++i) tmp += f[i][x%i];
			cout<<a[x] + tmp<<'\n';
		}
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T=1;
	//cin>>T;
	while(T--)
	{
		solve();
	}
}

图染色

题意:n个点,m条边的无向图,初始全为白点 操作1.翻转x 2.查询x的邻居有多少黑点

题解:对度数小的点查询直接遍历,对度数大的点在修改时维护出来,给每个点建大点图,每个点至多会有n条边

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int uint;
typedef vector<string> VS;
typedef vector<int> VI;
typedef vector<vector<int>> VVI;
const int N = 2e5+10;
const int B = 500;
bool st[N],big[N];
vector<int> e[N],bige[N];
int ans[N];
void solve()
{
	int n,m,q;
	cin>>n>>m>>q;
	
	while(m--)
	{
		int u,v;
		cin>>u>>v;
		e[u].push_back(v);
		e[v].push_back(u);
	}
	
	for(int i=1;i<=n;++i)
	{
		//判断一个点是不是大点
		big[i] = (e[i].size()>=B);
	}
	for(int i=1;i<=n;++i)
	{
		//建每个点的大点图来维护大点的邻居
		for(auto v:e[i]) if(big[v]) bige[i].push_back(v);
	}
	while(q--)
	{
		int op,x;
		cin>>op>>x;
		if(op&1) 
		{
			//对于每次翻转都维护其大点图
			st[x] = !st[x];
			for(auto v:bige[x]) 
			{
				if(st[x]) ans[v]++;
				else ans[v]--;
			}
		}
		else
		{
			//对于大点的答案直接输出
			if(big[x]) cout<<ans[x]<<'\n';
			else
			{
				//对于小点的查询直接遍历即可
				int tmp = 0;
			    for(auto v:e[x]) if(st[v]) tmp++;
			    cout<<tmp<<'\n';
			}
		}
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T=1;
	//cin>>T;
	while(T--)
	{
		solve();
	}
}

莫队、

莫队是解决容易完成区间添加和删除单个元素,但难以合并的问题,将区间合并的问题转化为元素的增加和减少

小z的袜子

题意:c[i]表示袜子的种类 每个询问求每次求区间[l,r]中选出两个相同类型的袜子概率

题解:将[l,r]按l分块,那么每次对一个块内的所有询问,l的下标移动是n的r的下标移动是n的,总体复杂度nn

优化:将偶数块的r按从小到大排序,奇数块从大道小排序可以减少r移动的次数

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int uint;
typedef vector<string> VS;
typedef vector<int> VI;
typedef vector<vector<int>> VVI;
const int N = 50010,Ba = 200;
int c[N],cnt[N];
ll ans[N][2];
vector<array<int,3>> qu;
void solve()
{
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;++i) cin>>c[i];
	
	for(int i=1;i<=m;++i)
	{
		int l,r;
		cin>>l>>r;
		//需要特判空区间,防止出现re
		if(l == r) {ans[i][0] = 0,ans[i][1] = 1;continue;}
		qu.push_back({l,r,i});
		//(l~r)总选择的情况
		ans[i][1] = (ll)(r-l+1)*(r-l)/2;
	}
	sort(qu.begin(),qu.end(),[&](array<int,3> A,array<int,3> B) 
	{ 
	  if(A[0]/Ba!=B[0]/Ba) return (A[0]/Ba < B[0]/Ba);
	  //若块为奇数就反序排列,块为偶数r从小到达排列
	  if((A[0]/Ba) & 1) return (A[1]>B[1]);
	  else return (A[1]<B[1]);
	});
	
	ll tmp = 0;
	//每新加进来一个数,可以成对的情况就多出cnt[c[x]]种
	auto add = [&](int x)->void
	{
		//添加的时候应该先加入tmp在自增cnt
		tmp += cnt[c[x]]++;
	};
	auto del = [&](int x)->void
	{
		//删除的时候应该先减小cnt在减去tmp
		tmp -= --cnt[c[x]];
	};
	int l = 1, r = 0;
	for(auto q:qu)
	{
		int x = q[0], y = q[1];
		int id = q[2];
		while(r < y) r++, add(r);
		while(l > x) l--, add(l);
		while(r > y) del(r), r--;
		while(l < x) del(l), l++;
		int G = __gcd(tmp,ans[id][1]);
		ans[id][0] = tmp/G;
		ans[id][1] = ans[id][1]/G;
	} 
	for(int i=1;i<=m;++i)
	{
		cout<<ans[i][0]<<'/'<<ans[i][1]<<'\n';
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T = 1;
	//cin>>T;
	while(T--)
	{
		solve();
	}
}

重复的数

题意:给定n个数 有m次询问 每次询问[l,r]里有多少个数的出现次数恰好为k

题解:莫队,维护区间每个数出现的次数以及区间内出现该次数的数字个数

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int uint;
typedef vector<string> VS;
typedef vector<int> VI;
typedef vector<vector<int>> VVI;
const int N = 1e5+10,Ba = 500;
int a[N],cnt[N],fre[N];
int ans[N];
vector<array<int,4>> qu;
void solve()
{
	int n,m;
	cin>>n;
	for(int i=1;i<=n;++i) cin>>a[i];
	
	cin>>m;
	for(int i=1;i<=m;++i)
	{
		int l,r,k;
		cin>>l>>r>>k;
		qu.push_back({l,r,k,i});
	}
	sort(qu.begin(),qu.end(),[&](array<int,4> A,array<int,4> B) 
	{ 
	  if(A[0]/Ba!=B[0]/Ba) return (A[0]/Ba < B[0]/Ba);
	  //若块为奇数就反序排列,块为偶数r从小到达排列
	  if((A[0]/Ba) & 1) return (A[1]>B[1]);
	  else return (A[1]<B[1]);
	});
	
	//记录每个出现频率的数
	auto add = [&](int x)->void
	{
		//增加的时候原本和a[x]的同频的个数减小,频数加一的个数加一
		fre[cnt[a[x]]]--;
		fre[++cnt[a[x]]]++;
	};
	auto del = [&](int x)->void
	{
		fre[cnt[a[x]]]--;
		fre[--cnt[a[x]]]++;
	};
	int l = 1, r = 0;
	for(auto q:qu)
	{
		int x = q[0], y = q[1], k = q[2];
		int id = q[3];
		while(r < y) r++, add(r);
		while(l > x) l--, add(l);
		while(r > y) del(r), r--;
		while(l < x) del(l), l++;
		ans[id] = fre[k];
	} 
	for(int i=1;i<=m;++i)
	{
		cout<<ans[i]<<'\n';
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T = 1;
	//cin>>T;
	while(T--)
	{
		solve();
	}
}

树上倍增

路径最小值

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int uint;
typedef vector<string> VS;
typedef vector<int> VI;
typedef vector<vector<int>> VVI;
const int N = 2e5+10;
vector<PII> e[N];
const int M = 20;
int par[20][N],val[20][N],dep[N];
int n;
void dfs(int u,int f)
{
	for(auto [v,w]:e[u])
	{
		if(v==f) continue;
		dep[v] = dep[u] + 1;
		par[0][v] = u;
		val[0][v] = w;
		dfs(v,u);
	}
}
void init_st()
{
	for(int j=1;j<M;++j)
	 for(int i=1;i<=n;++i)
	 {
	 	//注意此处要用祖先节点更新而不是下标增加
	 	val[j][i] = min(val[j-1][i],val[j-1][par[j-1][i]]);
	 	par[j][i] = par[j-1][par[j-1][i]];
	 }
}
int qu_tree(int u,int v)
{
	int ans = 1<<30;
	if(dep[u]<dep[v]) swap(u,v);
	int d =dep[u] - dep[v];
	
	for(int i=0;i<=20&&d;i++,d>>=1)
	{
		if(d&1) ans = min(val[i][u],ans), u = par[i][u];
	}
	
	if(u==v) return ans;
	
	for(int i=19;i>=0;--i)
	{
		if(par[i][u]!=par[i][v])
		{
			//注意应该先更新ans后跳节点
			ans = min({ans,val[i][u],val[i][v]});
			u = par[i][u], v = par[i][v];
		}
	}
	ans = min({ans,val[0][u],val[0][v]});
	return ans;
}
void solve()
{
	int q;
	cin>>n>>q;
	memset(val,0x3f,sizeof val);
	for(int i=1;i<n;++i)
	{
		int u,v,w;
		cin>>u>>v>>w;
		e[u].push_back({v,w});
		e[v].push_back({u,w});
	}
	
	dfs(1,0);
	init_st();
	//for(int i=1;i<=n;++i) cout<<i<<' '<<val[0][i]<<' '<<val[1][i]<<'\n';
	for(int i=0;i<q;++i)
	{
		int u,v;
		cin>>u>>v;
		cout<<qu_tree(u,v)<<'\n';
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T = 1;
	//cin>>T;
	while(T--)
	{
		solve();
	}
}

dfs序

题意:n个点,每个点w[i],操作1. 修改a[x] = y 2.查询x的子树和以及到根路径和

题解:按照dfs序每个点有l[i]和r[i],将子树和转化为BIT1的[li,ri]区间和,每次修改将BIT2的[li,ri]修改把路径和转化为单点求和

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int uint;
typedef vector<string> VS;
typedef vector<int> VI;
typedef vector<vector<int>> VVI;
const int N = 2e5+10;
vector<int> e[N];
template <class T>
struct BIT
{
	T c[N];
	int sz;
	void init(int s)
	{
		sz = s;
		for(int i=1;i<=sz;++i) c[i] = 0;
	}
	T lowbit(int x)
	{
		return x&-x;
	}
	T sum(int x) 
	{
		assert(x <= sz);
		T res=0;
		while(x) res+=c[x],x-=lowbit(x);
		return res;
	}
	void update(int x,int y) 
	{
		assert(x != 0);
		while(x<=sz) c[x]+=y,x+=lowbit(x);
	}
};
BIT <ll> c1, c2;
int a[N],l[N],r[N];
int tot;
void dfs(int u,int f)
{
	l[u] = ++tot;
	for(auto v:e[u])
	{
		if(v == f) continue;
		dfs(v,u);
	}
	r[u] = tot;
}
void solve()
{
	int n,q;
	cin>>n>>q;
	//使用封装BIT一定要init
	c1.init(n);c2.init(n);
    for(int i=0;i<n-1;++i) 
	{
		int u,v;
		cin>>u>>v;
		e[u].push_back(v);
		e[v].push_back(u);
	}
	dfs(1,0);
    for(int i=1;i<=n;++i) 
	{
		cin>>a[i];
    	c1.update(l[i],a[i]);
        c2.update(l[i],a[i]);
		c2.update(r[i]+1,-a[i]);
	}
	for(int i=1;i<=q;++i)
	{
		int op;
		cin>>op;
		if(op&1)
		{
			int x, y;
			cin>>x>>y;
			int d = y - a[x];
			a[x] = y;
			c1.update(l[x],d);
			c2.update(l[x],d);
			c2.update(r[x]+1,-d);
		}
		else
		{
            int x;
			cin>>x;
			cout<<c1.sum(r[x]) - c1.sum(l[x]-1)<<' '<<c2.sum(l[x])<<'\n';
		}
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T = 1;
	//cin>>T;
	while(T--)
	{
		solve();
	}
}

dfs序2

题意:n个点 w[i] 1为根 操作1.w[x] = y 2.求x的子树和 3.修改根为x

题解:最主要是换根问题,考虑若查询的x==root则ans = sum[n]否则若root在x的子树里(即l[root]>l[x]&&r[root]<=r[x])要找到root在x的哪个子节点y上,ans = sum[n] - sum[r[y]] - sum[l[y]-1]

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int uint;
typedef vector<string> VS;
typedef vector<int> VI;
typedef vector<vector<int>> VVI;
const int N = 2e5+10;
vector<int> e[N];
vector<PII> son[N];
template <class T>
struct BIT
{
	T c[N];
	int sz;
	void init(int s)
	{
		sz = s;
		for(int i=1;i<=sz;++i) c[i] = 0;
	}
	T lowbit(int x)
	{
		return x&-x;
	}
	T sum(int x) 
	{
		assert(x <= sz);
		T res=0;
		while(x) res+=c[x],x-=lowbit(x);
		return res;
	}
	void update(int x,int y) 
	{
		assert(x != 0);
		while(x<=sz) c[x]+=y,x+=lowbit(x);
	}
};
BIT <ll> c1;
int a[N],l[N],r[N];
int tot;
void dfs(int u,int f)
{
	l[u] = ++tot;
	for(auto v:e[u])
	{
		if(v == f) continue;
		dfs(v,u);
		son[u].push_back({l[v],r[v]});
	}
	r[u] = tot;
}
void solve()
{
	int n,q;
	cin>>n>>q;
	//使用封装BIT一定要init
	c1.init(n);
    for(int i=0;i<n-1;++i) 
	{
		int u,v;
		cin>>u>>v;
		e[u].push_back(v);
		e[v].push_back(u);
	}
	dfs(1,0);
    for(int i=1;i<=n;++i) 
	{
		cin>>a[i];
    	c1.update(l[i],a[i]);
	}
	int root = 1;
	for(int i=1;i<=q;++i)
	{
		int op;
		cin>>op;
		if(op == 1)
		{
			int x, y;
			cin>>x>>y;
			int d = y - a[x];
			a[x] = y;
			c1.update(l[x],d);
		}
		else if(op == 2)
		{
            int x;
			cin>>x;
			if(x == root) cout<<c1.sum(n)<<'\n';
			else if(l[root]>l[x]&&r[root]<=r[x])//如果root是x的子树
			{
				auto seg = *prev(upper_bound(son[x].begin(),son[x].end(),
				PII{l[root],r[root]}));
				cout<<c1.sum(n) - (c1.sum(seg.y) - c1.sum(seg.x - 1))<<'\n';
			}
			else
			{
				cout<<c1.sum(r[x]) - c1.sum(l[x]-1)<<'\n';
			}
		}
		else
		{
			int x;
			cin>>x;
			root = x;
		}
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T = 1;
	//cin>>T;
	while(T--)
	{
		solve();
	}
}

启发式合并

梦幻布丁

注意e[x].clear()不释放内存,可以用vector().swap(e[x])释放,swap时间复杂度是O(1)的

#include<bits/stdc++.h>

using namespace std;

const int N = 100010, M = 1000010;

int n, m;
vector<int> e[M];
int color[N];
int ans;

void add(int u, int v)
{
    e[u].push_back(v);
}

void merge(int x, int y)
{
    if (x == y) return;
    if (e[x].size() > e[y].size()) e[x].swap(e[y]);
    //保证是从x合并到y
    int col = color[e[y][0]];
    for (auto v:e[x])
    {
        //若有相邻则段数减一
        ans -= (color[v - 1] != color[v]) + (color[v + 1] != color[v]);
        color[v] = col;
        ans += (color[v - 1] != color[v]) + (color[v + 1] != color[v]);
    }
    for (auto v:e[x])
    {
        e[y].push_back(v);
    }
    vector<int>().swap(e[x]);
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    cin>>n>>m;
    for (int i = 1; i <= n; i ++ )
    {
        cin>>color[i];
        //把0和n+1考虑进来
        if (color[i] != color[i - 1]) ans ++;
        add(color[i], i);
    }
    ans++;
    //cout<<ans<<'\n';
    while (m -- )
    {
        int op;
        cin>>op;
        if (op == 2) cout<<ans-1<<'\n';
        else
        {
            int x, y;
            cin>>x>>y;
            merge(x, y);
        }
    }
    return 0;
}

路径最小值

题解:按边从大到小排序,每次将两个集合合并,此时的边就是询问点分别在两个集合中的路径最小值

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int uint;
typedef vector<string> VS;
typedef vector<int> VI;
typedef vector<vector<int>> VVI;
const int N = 2e5+10;
array<int,3> E[N];
int ans[N],f[N];
map<int,int> qu[N];
set<int> vec[N];

int find(int x)
{
	if(f[x]==x) return x;
	return f[x] = find(f[x]);
}

void solve()
{
	int n,q;
	cin>>n>>q;
	//由于是按照边从大到小合并所以开始所有集合元素都是自己
	for(int i=1;i<=n;++i) f[i] = i, vec[i].insert(i);
	for(int i=0;i<n-1;++i)
	{
		int u,v,w;
		cin>>u>>v>>w;
		E[i][0] = w,E[i][1] = u,E[i][2] = v;
	}
	for(int i=0;i<q;++i) 
	{
		int u,v;
		cin>>u>>v;
		qu[u][i] = v;
		qu[v][i] = u;
	}
	sort(E,E+n-1);
	reverse(E,E+n-1);
	
	for(int i=0;i<n-1;++i)
	{
		int u = E[i][1], v = E[i][2], w = E[i][0];
		//寻找到已经合并完成的集合
		u = find(u), v = find(v);
		if(vec[u].size() > vec[v].size()) swap(u,v);
		
		for(auto [id,t]:qu[u])
		{
			if(vec[v].count(t)) ans[id] = w, qu[v].erase(id);
			else qu[v][id] = t;
		}
		qu[u].clear();
		for(auto t:vec[u]) vec[v].insert(t);
		vec[u].clear();
		f[u] = v;
	}
	for(int i=0;i<q;++i) cout<<ans[i]<<"\n";
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T = 1;
	//cin>>T;
	while(T--)
	{
		solve();
	}
}

优化

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int uint;
typedef vector<string> VS;
typedef vector<int> VI;
typedef vector<vector<int>> VVI;
const int N = 2e5+10;
array<int,3> E[N];
int ans[N],f[N],belong[N];
vector<PII> qu[N];
vector<int> vec[N];

int find(int x)
{
	if(f[x]==x) return x;
	return f[x] = find(f[x]);
}

void solve()
{
	int n,q;
	cin>>n>>q;
	//由于是按照边从大到小合并所以开始所有集合元素都是自己
	for(int i=1;i<=n;++i) 
	{
		f[i] = i, belong[i] = i;
		vec[i].push_back(i);
	}
	for(int i=0;i<n-1;++i)
	{
		int u,v,w;
		cin>>u>>v>>w;
		E[i][0] = w,E[i][1] = u,E[i][2] = v;
	}
	for(int i=0;i<q;++i) 
	{
		int u,v;
		cin>>u>>v;
		qu[u].push_back({i,v});
		qu[v].push_back({i,u});
	}
	sort(E,E+n-1);
	reverse(E,E+n-1);
	
	for(int i=0;i<n-1;++i)
	{
		int u = E[i][1], v = E[i][2], w = E[i][0];
		//寻找到已经合并完成的集合
		u = find(u), v = find(v);
		if(vec[u].size() > vec[v].size()) swap(u,v);
		//只需要查找存的点是否在v集合,使用并查集不需要用set
		for(auto [id,t]:qu[u])
		{
			if(ans[id]) continue;
			if(belong[t] == v) ans[id] = w;
			else qu[v].push_back({id,t});
		}
		qu[u].clear();
		for(auto t:vec[u]) 
		{
			belong[t] = v;vec[v].push_back(t);
		}
		vec[u].clear();
		f[u] = v;
	}
	for(int i=0;i<q;++i) cout<<ans[i]<<"\n";
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T = 1;
	//cin>>T;
	while(T--)
	{
		solve();
	}
}

启发式分治

好序列

若能使得T(n) = T(x) + T(n-x) + O(min(x,n-x)),则复杂度为nlogn

题意:一个长为n的序列。定义一个序列是好的, 当且仅当他的每一个子区间满足,至少存在一个元素x仅出现了一次。求是否为好序列

若有一个数a[i]在[1,n]中只出现了一次,则所有包含a[i]的区间都是合法的,只需考虑[1,i-1]和[i+1,n]

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int uint;
typedef vector<string> VS;
typedef vector<int> VI;
typedef vector<vector<int>> VVI;
const int N = 2e5+10;
int a[N],pre[N],nxt[N];
bool deal(int l,int r)
{
	if(l > r) return true;
	for(int i = l,j = r;i <= j; i++, j--)
	{
		//注意此处i和j的处理是独立的
		if(pre[i]<l && nxt[i]>r) return deal(l,i-1)&&deal(i+1,r);
		if(pre[j]<l && nxt[j]>r) return deal(l,j-1)&&deal(j+1,r);
	}
	return false;
};
void solve()
{
	int n;
	cin>>n;
	map<int,int> pos;
	for(int i=1;i<=n;++i) 
	{
		cin>>a[i];
		if(pos.find(a[i])!=pos.end()) pre[i] = pos[a[i]];
		else pre[i] = 0;
		pos[a[i]] = i;
	}
	pos.clear();
	for(int i=n;i>=1;--i)
	{
		if(pos.find(a[i])!=pos.end()) nxt[i] = pos[a[i]];
		else nxt[i] = n+1;
		pos[a[i]] = i;
	}
	cout<<(deal(1,n)?("non-boring\n"):("boring\n"));
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T;
	cin>>T;
	while(T--)
	{
		solve();
	}
}

DSU on tree

Lomsat gelral

题意:n个节点的树,每个点有一个颜色,求每个点子树颜色的众数和

题解:保留重儿子舍去轻儿子,做启发式合并

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int uint;
typedef vector<string> VS;
typedef vector<int> VI;
typedef vector<vector<int>> VVI;
const int N = 2e5+10;
vector<int> e[N];
int sz[N],son[N],l[N],r[N],id[N];
int c[N],cnt[N];//cnt[x]表示颜色X出现的次数
ll tot,maxsum,maxcnt;//maxcnt记录出现的最大次数,summax记录答案
ll ans[N];
void dfs_init(int u,int f)
{
	l[u] = ++tot;
	id[tot] = u;
	sz[u]++;
	for(auto v:e[u])
	{
		if(v==f) continue;
		dfs_init(v,u);
		if(sz[v]>sz[son[u]]) son[u] = v;
		sz[u] += sz[v];
	}
	r[u] = tot;
}
void dsu_on_tree(int u,int f,bool keep)
{
	for(auto v:e[u])
	{
		if(v==f||v==son[u]) continue;
		dsu_on_tree(v,u,0);//轻儿子的数据舍去
	}
	//如果存在重儿子
	if(son[u]) dsu_on_tree(son[u],u,1);
	
	auto add = [&](int x)
	{
		x = c[x];
		cnt[x]++;
		if(cnt[x] > maxcnt) maxcnt = cnt[x], maxsum = x;
		else if(cnt[x] == maxcnt) maxsum += x; 
	};
	auto del = [&](int x)
	{
		x = c[x];
		cnt[x]--;
	};
	//把u的所有轻子树的所有节点都放到重儿子的集合中
	for(auto v:e[u])
	{
		if(v==f||v==son[u]) continue;
		//通过dfs序来减少递归次数
		for(int i = l[v];i <= r[v]; ++i) add(id[i]);
	}
	add(u);
	//kepp=0表示该树要舍去包括maxcnt和maxsum,这两个数据应该在合并的时候统计
	ans[u] = maxsum;
	if(!keep)
	{
		maxcnt = 0, maxsum = 0;
		for(int i = l[u];i <= r[u]; ++i) del(id[i]);
	}
}
void solve()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;++i) cin>>c[i];
	for(int i=1;i<=n-1;++i)
	{
		int u,v;
		cin>>u>>v;
		e[u].push_back(v);
		e[v].push_back(u);
	}
	dfs_init(1,0);
	dsu_on_tree(1,0,0);
	
	for(int i=1;i<=n;++i) cout<<ans[i]<<" ";
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T = 1;
	//cin>>T;
	while(T--)
	{
		solve();
	}
}

树链剖分

类似启发式合并的思想,从一个节点出发,经过的轻链个数至多logn次

LCA

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int uint;
typedef vector<string> VS;
typedef vector<int> VI;
typedef vector<vector<int>> VVI;
const int N = 2e5+10;
vector<int> e[N];
int tot,l[N],r[N],id[N];//建线段树所需
int top[N],sz[N],son[N],fa[N],dep[N];
void dfs1(int u,int f)
{
	sz[u]++;
	fa[u] = f;
	for(auto v:e[u])
	{
		if(v==f) continue;
		dep[v] = dep[u] + 1;
		dfs1(v,u);
		if(sz[v]>sz[son[u]]) son[u] = v;
		sz[u] += sz[v];
	}
}
void dfs2(int u,int f,int t)
{
	l[u] = ++tot;
	id[tot] = u;
	top[u] = t;
	if(son[u])
	{
	    //重儿子保留其父亲的top节点
		dfs2(son[u],u,t);
	}
	for(auto v:e[u])
	{
		if(v==f||v==son[u]) continue;
		dfs2(v,u,v);
	}
	r[u] = tot;
}
int LCA(int u,int v)
{	
	while(top[u]!=top[v])
	{
		//每次让深度更深的点跳
		if(dep[top[u]]<dep[top[v]]) v = fa[top[v]];
		else u = fa[top[u]];
	}
	if(dep[u]<dep[v]) return u;
	else return v;
}
void solve()
{
	int n,q;
	cin>>n;
	for(int i=0;i<n-1;++i)
	{
		int u,v;
		cin>>u>>v;
		e[u].push_back(v);
		e[v].push_back(u);
	}
	dfs1(1,0);
	dfs2(1,0,1);
	cin>>q;
	for(int i=0;i<q;++i)
	{
		int u,v;
		cin>>u>>v;
		cout<<LCA(u,v)<<'\n';
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T = 1;
	//cin>>T;
	while(T--)
	{
		solve();
	}
}

ZJOI2008, 树的统计

树链剖分板子, 注意线段树上的标号是dfs序

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int uint;
typedef vector<string> VS;
typedef vector<int> VI;
typedef vector<vector<int>> VVI;
const int N = 2e5+10;
vector<int> e[N];
int tot,l[N],r[N],id[N];//建线段树所需
int top[N],sz[N],son[N],fa[N],dep[N];
void dfs1(int u,int f)
{
	sz[u]++;
	fa[u] = f;
	for(auto v:e[u])
	{
		if(v==f) continue;
		dep[v] = dep[u] + 1;
		dfs1(v,u);
		if(sz[v]>sz[son[u]]) son[u] = v;
		sz[u] += sz[v];
	}
}
void dfs2(int u,int f,int t)
{
	l[u] = ++tot;
	id[tot] = u;
	top[u] = t;
	if(son[u])
	{
	    //重儿子保留其父亲的top节点
		dfs2(son[u],u,t);
	}
	for(auto v:e[u])
	{
		if(v==f||v==son[u]) continue;
		dfs2(v,u,v);
	}
	r[u] = tot;
}
int a[N];
struct info
{
	ll maxn,sum;
};
info operator + (const info& l,const info& r)
{
	return (info){max(l.maxn,r.maxn),l.sum+r.sum};
}
struct node
{
	int l,r;
	info val;
}tr[N<<2];
void pushup(int p)
{
	tr[p].val = tr[p<<1].val + tr[p<<1|1].val;
}
void build(int l,int r,int p)
{
	tr[p].l = l,tr[p].r = r;
	if(l==r)
	{
		tr[p].val = (info){a[id[l]], a[id[l]]};
		return ;
	}
	int m = (l+r)/2;
	build(l,m,p<<1);
	build(m+1,r,p<<1|1);
	pushup(p);
}
//单点修改
void update(int x,int d,int p)
{
	int l = tr[p].l, r = tr[p].r;
	if(l==r)
	{
		tr[p].val = (info){d, d};
		return ;
	}
	int m = (l+r)/2;
	if(x<=m) update(x,d,p<<1);
	else update(x,d,p<<1|1);
	pushup(p);
}
info query(int x,int y,int p)
{
	int l = tr[p].l,r = tr[p].r;
	if(x==l&&r==y) return tr[p].val;
	//info ans;
	int m = (l+r)/2;
	if(y<=m) return query(x,y,p<<1);
	else if(x>m) return query(x,y,p<<1|1);
	else return query(x,m,p<<1)+query(m+1,y,p<<1|1);
}
info query(int u,int v)
{
	info ans{(ll)-1e9,0};
	while(top[u]!=top[v])
	{
		//每次让深度更深的点跳
		if(dep[top[u]]<dep[top[v]]) 
		{
			ans = ans + query(l[top[v]],l[v],1);
			v = fa[top[v]];
		}
		else 
		{
			ans = ans + query(l[top[u]],l[u],1);
			u = fa[top[u]];
		}
	}
	if(dep[u]<dep[v]) return ans + query(l[u],l[v],1);
	else return ans + query(l[v],l[u],1);
}
void solve()
{
	int n,q;
	cin>>n;
	for(int i=0;i<n-1;++i)
	{
		int u,v;
		cin>>u>>v;
		e[u].push_back(v);
		e[v].push_back(u);
	}
	for(int i=1;i<=n;++i) cin>>a[i];
	dfs1(1,0);
	dfs2(1,0,1);
	build(1,n,1);
	cin>>q;
	for(int i=0;i<q;++i)
	{
		int u,v;
		char op[10];
		cin>>op>>u>>v;
		if(op[0]=='C') 
		{
			//注意在线段树上修改的位置是dfs序
			update(l[u],v,1);
		}
		else if(op[1]=='M') 
		{
			cout<<query(u,v).maxn<<'\n';
		}
		else cout<<query(u,v).sum<<'\n';
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T = 1;
	//cin>>T;
	while(T--)
	{
		solve();
	}
}

SDOI 染色

树链剖分之区间更新,注意区间和并时的顺序

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int uint;
typedef vector<string> VS;
typedef vector<int> VI;
typedef vector<vector<int>> VVI;
const int N = 2e5+10;
vector<int> e[N];
int tot,l[N],r[N],id[N];//建线段树所需
int top[N],sz[N],son[N],fa[N],dep[N];
void dfs1(int u,int f)
{
	sz[u]++;
	fa[u] = f;
	for(auto v:e[u])
	{
		if(v==f) continue;
		dep[v] = dep[u] + 1;
		dfs1(v,u);
		if(sz[v]>sz[son[u]]) son[u] = v;
		sz[u] += sz[v];
	}
}
void dfs2(int u,int f,int t)
{
	l[u] = ++tot;
	id[tot] = u;
	top[u] = t;
	if(son[u])
	{
	    //重儿子保留其父亲的top节点
		dfs2(son[u],u,t);
	}
	for(auto v:e[u])
	{
		if(v==f||v==son[u]) continue;
		dfs2(v,u,v);
	}
	r[u] = tot;
}
int a[N];
struct info
{
	int l_col,r_col,seg;
	info() {}
	info(int a) :l_col(a),r_col(a),seg(0) {}
	info(int a,int b,int c) :l_col(a),r_col(b),seg(c) {}
};
struct tag
{
	int col;
	tag() {}
	tag(int a):col(a) {}
};
//线段树如果wa掉一定先检查这三个重载
//用于合并左右子树
info operator + (const info& l,const info& r)
{
	return info(l.l_col,r.r_col,l.seg+r.seg + (l.r_col != r.l_col));
}
info operator + (const info &v,const tag& t)
{
	return info(t.col,t.col,0);
}
//下推标记时标记的合并,t1被t2作用
tag operator + (const tag &t1,const tag&t2)
{
	return t2;
}
struct node
{
	int l,r;
	info val;
	tag t;
}tr[N<<2];
void settag(int p,tag t)
{
	tr[p].val = tr[p].val + t;
	//注意此处的顺序要用新的tag更新原本的tag
	tr[p].t = tr[p].t + t;
}
//一般不用修改
void pushup(int p)
{
	tr[p].val = tr[p<<1].val + tr[p<<1|1].val;
}
void pushdown(int p)
{
	if(tr[p].t.col)
	{
	settag(p<<1,tr[p].t);
	settag(p<<1|1,tr[p].t);
	tr[p].t.col = 0; //下推完记得清空
	}
}
void build(int l,int r,int p)
{
	tr[p].l = l,tr[p].r = r;
	if(l==r)
	{
		tr[p].val = info(a[id[l]]);
		return ;
	}
	int m = (l+r)/2;
	build(l,m,p<<1);
	build(m+1,r,p<<1|1);
	pushup(p);
}
void update(int x,int y,tag C,int p)
{
	int l = tr[p].l, r = tr[p].r;
	if(l==x&&r==y) 
	{
		settag(p,C);
		return ;
	}
	pushdown(p);
	
	int m = (l+r)/2;
	if(y<=m) update(x,y,C,p<<1);
	else if(x>m) update(x,y,C,p<<1|1);
	else 
	{
		update(x,m,C,p<<1);
		update(m+1,y,C,p<<1|1);
	}
	pushup(p);
}
info query(int x,int y,int p)
{
	int l = tr[p].l,r = tr[p].r;
	if(x==l&&r==y) return tr[p].val;
	pushdown(p);
	info ans;
	int m = (l+r)/2;
	if(y<=m) return query(x,y,p<<1);
	else if(x>m) return query(x,y,p<<1|1);
	else return query(x,m,p<<1)+query(m+1,y,p<<1|1);
}
void update(int u,int v,tag C)
{
	while(top[u]!=top[v])
	{
		//每次让u跳,更新时深度更深的dfs序为右区间
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		update(l[top[u]],l[u],C,1);
		u = fa[top[u]];
	}
	if(dep[u]<dep[v]) swap(u,v);
	update(l[v],l[u],C,1);
}
int query(int u,int v)
{
	info ansu(0,0,-1),ansv(0,0,-1);
	if(l[u]>l[v]) swap(u,v);
	//合并时有顺序,保持query + ans相当于从LCA方向为左
	//当前点为右的线段
	while(top[u]!=top[v])
	{
		if(dep[top[u]]<dep[top[v]]) 
		{
			ansv = query(l[top[v]],l[v],1) + ansv;
			v = fa[top[v]];
		}
		else 
		{
			ansu = query(l[top[u]],l[u],1) + ansu;
			u = fa[top[u]];
		}
	}
	if(dep[u] < dep[v]) ansv = query(l[u],l[v],1) + ansv;
	else ansu = query(l[v],l[u],1) + ansu;
	return ansu.seg + ansv.seg + (ansu.l_col != ansv.l_col) + 1;
}
void solve()
{
	int n,q;
	cin>>n>>q;
	for(int i=1;i<=n;++i) cin>>a[i];
	for(int i=0;i<n-1;++i)
	{
		int u,v;
		cin>>u>>v;
		e[u].push_back(v);
		e[v].push_back(u);
	}
	dfs1(1,0);
	dfs2(1,0,1);
	build(1,n,1);
	for(int i=0;i<q;++i)
	{
		char op;
		cin>>op;
		if(op == 'C') 
		{
			//注意在线段树上修改的位置是dfs序
			int a,b,c;
			cin>>a>>b>>c;
			update(a,b,(tag){c});
		}
		else
		{
			int a,b;
			cin>>a>>b;
			cout<<query(a,b)<<'\n';
		}
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T = 1;
	//cin>>T;
	while(T--)
	{
		solve();
	}
}
 posted on   ruoye123456  阅读(11)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
点击右上角即可分享
微信分享提示