jiedai算法模板合集(正在肝2021.8.15)

文章目录

基础模板

常用板子

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
template<class T>inline void MAX(T &x,T y){if(y>x)x=y;}
template<class T>inline void MIN(T &x,T y){if(y<x)x=y;}
template<class T>inline void rd(T &x){
	x=0;char o,f=1;
	while(o=getchar(),o<48)if(o==45)f=-f;
	do x=(x<<3)+(x<<1)+(o^48);
	while(o=getchar(),o>47);
	x*=f;
}
template<class T>inline void print(T x,bool op=1){
	static int top,stk[105];
	if(x<0)x=-x,putchar('-');
	if(x==0)putchar('0');
	while(x)stk[++top]=x%10,x/=10;
	while(top)putchar(stk[top--]+'0');
	putchar(op?'\n':' ');
}

signed main(){
#ifndef ONLINE_JUDGE
	freopen("jiedai.in","r",stdin);
#endif
	
	
	return (0-0);
}

数学题常用板子

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
template<class T>inline void MAX(T &x,T y){if(y>x)x=y;}
template<class T>inline void MIN(T &x,T y){if(y<x)x=y;}
template<class T>inline void rd(T &x){
	x=0;char o,f=1;
	while(o=getchar(),o<48)if(o==45)f=-f;
	do x=(x<<3)+(x<<1)+(o^48);
	while(o=getchar(),o>47);
	x*=f;
}
template<class T>inline void print(T x,bool op=1){
	static int top,stk[105];
	if(x<0)x=-x,putchar('-');
	if(x==0)putchar('0');
	while(x)stk[++top]=x%10,x/=10;
	while(top)putchar(stk[top--]+'0');
	putchar(op?'\n':' ');
}
const int SIZE=1e6+5;
const int P=1e9+7;
//const int P=998244353;
int fac[SIZE],inv[SIZE];
int fast(int a,int b=P-2){
	int res=1;
	while(b){
		if(b&1)res=1ll*res*a%P;
		a=1ll*a*a%P;
		b>>=1;
	}
	return res;
}
int comb(int n,int m){
	if(n<0||m<0||n<m)return 0;
	return 1ll*fac[n]*inv[m]%P*inv[n-m]%P;
}
ll gcd(ll n,ll m){
	if(m==0)return n;
	return gcd(m,n%m);
}

signed main(){
#ifndef ONLINE_JUDGE
	freopen("jiedai.in","r",stdin);
#endif
	fac[0]=1;
	for(int i=1;i<SIZE;i++)fac[i]=1ll*fac[i-1]*i%P;
	inv[SIZE-1]=fast(fac[SIZE-1]);
	for(int i=SIZE-2;i>=0;i--)inv[i]=1ll*inv[i+1]*(i+1)%P;
	
	return (0-0);
}

输出挂

可以输出__int128

template<class T>inline void print(T x,bool op=1){
	static int top,stk[105];
	if(x<0)x=-x,putchar('-');
	if(x==0)putchar('0');
	while(x)stk[++top]=x%10,x/=10;
	while(top)putchar(stk[top--]+'0');
	putchar(op?'\n':' ');
}

fread快读

fread函数从文件中读入

fread在读取换行的时候可能会出问题(慎用)

int Tot;
char buf[50000005],*ch=buf-1;
template<class T>inline void rd(T &x){
	int f=1;
	for(++ch;*ch<48&&ch<buf+Tot;++ch)if(*ch==45)f=-f;
	if(ch==buf+Tot)return;
	for(x=0;*ch>47;++ch)x=(x<<3)+(x<<1)+(*ch^48);
	x*=f;
}

	Tot=fread(buf,1,5e7,stdin);

高精度

struct INT{
	static const int base=1e4;
	vector<int>v;
	INT(ll x=0){
		while(x)v.push_back(x%base),x/=base;
		if(v.size()==0)v.push_back(0);
	}
	inline int size()const{return v.size();}
	inline void resize(int sz=1){v.clear();v.resize(sz,0);}
	inline void topzero(){while(v.size()>1&&v.back()==0)v.pop_back();}
	INT operator +(const INT &A)const{
		INT T;
		T.resize(max(size(),A.size())+1);
		for(int i=0;i<T.size()-1;i++){
			if(i<size())T.v[i]+=v[i];
			if(i<A.size())T.v[i]+=A.v[i];
			if(T.v[i]>=base)T.v[i+1]++,T.v[i]-=base;
		}
		T.topzero();
		return T;
	}
	INT operator *(const INT &A)const{
		INT T;
		T.resize(size()+A.size()+5);
		for(int i=0;i<size();i++)for(int j=0;j<A.size();j++){
			T.v[i+j]+=v[i]*A.v[j];
			if(T.v[i+j]>=2e9)T.v[i+j+1]+=T.v[i+j]/base,T.v[i+j]%=base;
		}
		for(int i=0;i<T.size();i++){
			if(T.v[i]>=base)T.v[i+1]+=T.v[i]/base,T.v[i]%=base;
		}
		T.topzero();
		return T;
	}
	INT operator /(const int x)const{
		INT res;
		res.resize(size());
		ll tmp=0;
		for(int i=size()-1;i>=0;i--){
			tmp=tmp*base+v[i];
			res.v[i]=tmp/x,tmp%=x;
		}
		res.topzero();
		return res;
	}
	int operator %(const int mod)const{
		int res=0;
		for(int i=size()-1;i>=0;i--)res=(1ll*res*base+v[i])%mod;
		return res;
	}
	friend INT fast(INT a,ll b){
		INT res(1);
		while(b){
			if(b&1)res=res*a;
			a=a*a;
			b>>=1;
		}
		res.topzero();
		return res;
	}
	void read(){
		string str;
		cin>>str;
		resize();
		for(int i=0;i<str.size();i++)*this=*this*INT(10)+INT(str[i]-'0');
	}
	void print(){
		printf("%d",v[size()-1]);
		for(int i=size()-2;i>=0;i--)printf("%04d",v[i]);
	}
};

分数类

适合打表时使用,运算范围在__int128以内

将define去掉后,运算范围在long long以内

struct frac{
	#define ll __int128
	ll a,b;
	frac(ll _a=0,ll _b=1):a(_a),b(_b){}
	ll gcd(ll a,ll b){
		if(b==0)return a;
		return gcd(b,a%b);
	}
	frac work(ll a,ll b){
		if(b<0)a=-a,b=-b;
		ll tmp=gcd(a,b);
		if(tmp<0)tmp=-tmp;
		return (frac){a/tmp,b/tmp};
	}
	frac operator +(frac A){return work(a*A.b+b*A.a,b*A.b);}
	frac operator -(frac A){return work(a*A.b-b*A.a,b*A.b);}
	frac operator *(frac A){return work(a*A.a,b*A.b);}
	frac operator *(ll x){return work(a*x,b);}
	frac operator /(frac A){return work(a*A.b,b*A.a);}
	frac operator /(ll x){return work(a,b*x);}
	void pt(ll x){
		static int stk[105],top;
		if(x==0)printf("0");
		if(x<0)x=-x,printf("-");
		while(x)stk[++top]=x%10,x/=10;
		while(top)printf("%d",stk[top--]);
	}
	void pt(){
		pt(a),printf("/"),pt(b),printf("  ");
	}
	#undef ll
};

打表压缩

代码一:数值小

/*
	利用44进制对打表的数组进行压缩,大于等于44的数表示分隔
	对于int范围以内的数压缩效果较好
	减号 '-'(45) 表示负号
	去掉 '"'(34) '/'(47) '?'(63) '\'(92)
	少了 ' '(32) '~'(126)
*/
void encode(ll *A,char *str,int n){
	for(int i=1,len=0;i<=n;i++){
		ll x=A[i];
		if(x<0)str[len++]='-',x=-x;
		if(x==0)str[len++]=81;
		while(x){
			int t=x%44;
			x/=44;
			if(x==0)t+=44;
			str[len++]=t+33+(t>=1)+(t>=11)+(t>=12)+(t>=27)+(t>=55);
		}
	}
}
int decode(char *str,ll *A){
	int n=0,len=strlen(str);
	for(int l=0,r=0,f=1;l<len;l=r+1,r=l,f=1){
		if(str[l]=='-')f=-1,l++,r++;
		while(r+1<len&&str[r]<81)r++;
		ll x=0;
		for(int i=r;i>=l;i--){
			int t=str[i]-33;
			t-=(t>=1),t-=(t>=11),t-=(t>=12),t-=(t>=27),t-=(t>=55);
			x=x*44+(t%44);
		}
		A[++n]=f*x;
	}
	return n;
}

代码二:数值大

/*
	利用89进制对打表的数组进行压缩,最大限度利用可显示字符
	对于long long范围以内的大整数压缩效果较好
	空格 ' '(32) 表示数字分隔符
	减号 '-'(45) 表示负号
	去掉 '"'(34) '/'(47) '?'(63) '\'(92)
*/
void encode(ll *A,char *str,int n){
	for(int i=1,len=0;i<=n;i++){
		ll x=A[i];
		if(x<0)str[len++]='-',x=-x;
		if(x==0)str[len++]=33;
		while(x){
			int t=x%89;
			x/=89;
			str[len++]=t+33+(t>=1)+(t>=11)+(t>=12)+(t>=27)+(t>=55);
		}
		str[len++]=' ';
	}
}
int decode(char *str,ll *A){
	int n=0,len=strlen(str);
	for(int l=0,r=0,f=1;l<len;l=r+2,r=l,f=1){
		if(str[l]=='-')f=-1,l++,r++;
		while(r+1<len&&str[r+1]!=' ')r++;
		ll x=0;
		for(int i=r;i>=l;i--){
			int t=str[i]-33;
			t-=(t>=1),t-=(t>=11),t-=(t>=12),t-=(t>=27),t-=(t>=55);
			x=x*89+t;
		}
		A[++n]=f*x;
	}
	return n;
}

基数排序

给 int 范围内的非负整数排序,建议 n>=1e5

void Sort(int n,int *A){
	static const int M=1e6+5,S=16,P=(1<<S)-1;
	static int cnt[1<<S],B[M];
	memset(cnt,0,sizeof(cnt));
	for(int i=1;i<=n;i++)cnt[A[i]&P]++;
	for(int i=1;i<=P;i++)cnt[i]+=cnt[i-1];
	for(int i=n;i>=1;i--)B[cnt[A[i]&P]--]=A[i];
	memset(cnt,0,sizeof(cnt));
	for(int i=1;i<=n;i++)cnt[B[i]>>S]++;
	for(int i=1;i<=P;i++)cnt[i]+=cnt[i-1];
	for(int i=n;i>=1;i--)A[cnt[B[i]>>S]--]=B[i];
}

杂项

优先队列小顶堆

priority_queue<int,vector<int>,greater<int> >Q;

二进制数中1的个数

inline int popcount(unsigned int x){
	return __builtin_popcount(x);
}
inline int popcountll(unsigned long long x){
	return __builtin_popcountll(x);
}

二进制数中末尾0的个数

__builtin_ctzll(0)的返回值为64

inline int ctzll(unsigned long long x){
	return __builtin_ctzll(x);
}

区间第k小函数

O(length)选出中点并将数组分成两边,使k左边的元素小等于等A[k],使k右边的元素大于等于A[k]

	nth_element(A+l,A+k,A+r+1);

归并函数

	merge(A+1,A+n+1,B+1,B+m+1,C+1,cmp);
	inplace_merge(A+l,A+mid+1,A+r+1,cmp);

大随机数

rng()返回一个unsigned int范围内的随机数,搭配取模使用

mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());

随机打乱数组元素顺序

mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());

	shuffle(A+1,A+n+1,rng);
	srand(time(NULL));
	random_shuffle(A+1,A+n+1);//n大时打乱效果不佳

程序运行时间

inline double run_time(){
    return 1.0*clock()/CLOCKS_PER_SEC;
}

上/下一个排列

	prev_permutation(A+1,A+n+1);//上一个排列
	next_permutation(A+1,A+n+1);//下一个排列
    for(int i=1;i<=n;i++)A[i]=i;
    do{
		
    }while(next_permutation(A+1,A+n+1));//枚举所有排列

string转char

	string str="jiedai";
	puts(str.data());

数据结构

树状数组

一维树状数组

用法一:单点修改,区间查询

单点修改:add(bit,x,v)

区间查询:query(bit,r)-query(bit,l-1)

用法二:区间修改,单点查询

区间修改:add(bit,l,v),add(bit,r+1,-v)

单点查询:query(bit,x)

int limit;//树状数组下标的上界
void add(ll *bit,int x,ll v){
	while(x<=limit)bit[x]+=v,x+=x&-x;
}
ll query(ll *bit,int x){
	ll res=0;
	while(x)res+=bit[x],x-=x&-x;
	return res;
}

用法三:区间修改,区间查询

令数组 a a a 的差分数组为 d d d ,即 a [ n ] = ∑ i = 1 n d [ i ] a[n]=\sum\limits_{i=1}^{n}{d[i]} a[n]=i=1nd[i]

∑ i = 1 r a [ i ] = ∑ i = 1 r ∑ j = 1 i d [ j ] \sum\limits_{i=1}^{r}{a[i]}=\sum\limits_{i=1}^{r}{\sum\limits_{j=1}^{i}{d[j]}} i=1ra[i]=i=1rj=1id[j] = ∑ i = 1 r ( r + 1 − i ) ∗ d [ i ] =\sum\limits_{i=1}^{r}{(r+1-i)*d[i]} =i=1r(r+1i)d[i] = ( r + 1 ) ∗ ∑ i = 1 r d [ i ] − ∑ i = 1 r d [ i ] ∗ i =(r+1)*\sum\limits_{i=1}^{r}{d[i]}-\sum\limits_{i=1}^{r}{d[i]*i} =(r+1)i=1rd[i]i=1rd[i]i

b i t 1 bit1 bit1 来维护 d [ i ] d[i] d[i] 的前缀和,用 b i t 2 bit2 bit2 来维护 d [ i ] ∗ i d[i]*i d[i]i 的前缀和

void real_add(ll *bit1,ll *bit2,int l,int r,ll v){
	add(bit1,l,v);
	add(bit2,l,v*l);
	add(bit1,r+1,-v);
	add(bit2,r+1,-v*(r+1));
}
ll real_query(ll *bit1,ll *bit2,int l,int r){
	return (r+1)*query(bit1,r)-query(bit2,r)-l*query(bit1,l-1)+query(bit2,l-1);
}
struct BIT{
	static const int SIZE=1e6+5;
	ll bit1[SIZE],bit2[SIZE];
	int limit;
	void init(int n=SIZE-1){
		limit=n;
		for(int i=1;i<=n;i++)bit1[i]=bit2[i]=0;
	}
	BIT(){init();}
	void add(ll *bit,int x,ll v){
		while(x<=limit)bit[x]+=v,x+=x&-x;
	}
	ll query(ll *bit,int x){
		ll res=0;
		while(x)res+=bit[x],x-=x&-x;
		return res;
	}
	void add(int l,int r,ll v){
		add(bit1,l,v);
		add(bit2,l,v*l);
		add(bit1,r+1,-v);
		add(bit2,r+1,-v*(r+1));
	}
	ll query(int l,int r){
		return (r+1)*query(bit1,r)-query(bit2,r)-l*query(bit1,l-1)+query(bit2,l-1);
	}
}bit;

用法四:求第一个前缀和大于等于某个值的下标

int sum_lower_bound(ll *bit,ll v){
	int pos=0;
	for(int i=20;i>=0;i--)
		if(pos+(1<<i)<=limit&&bit[pos+(1<<i)]<v)
			v-=bit[pos|=1<<i];
	return pos+1;
}

二维树状数组

用法一:单点修改,区间查询

单点修改:add(x,y,v)

区间查询:query(x2,y2)+query(x1-1,y1-1)-query(x1-1,y2)-query(x2,y1-1)

用法二:区间修改,单点查询

区间修改:add(x1,y1,v),add(x2+1,y2+1,v),add(x1,y2+1,-v),add(x2+1,y1,-v)

单点查询:query(x,y)

const int SIZE=5005;
int limit1=SIZE-1,limit2=SIZE-1;//两个维度的下标的上界
ll bit[SIZE][SIZE];

void add(int x,int y,ll v){
	for(int i=x;i<=limit1;i+=i&-i)
		for(int j=y;j<=limit2;j+=j&-j)
			bit[i][j]+=v;
}
ll query(int x,int y){
	ll res=0;
	for(int i=x;i;i-=i&-i)
		for(int j=y;j;j-=j&-j)
			res+=bit[i][j];
	return res;
}

用法三:区间修改,区间查询

令数组 a a a 的二维差分数组为 d d d ,即 a [ n ] [ m ] = ∑ i = 1 n ∑ j = 1 m d [ i ] [ j ] a[n][m]=\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}{d[i][j]} a[n][m]=i=1nj=1md[i][j]

∑ i = 1 x ∑ j = 1 y a [ i ] [ j ] = ∑ i = 1 x ∑ j = 1 y ∑ k = 1 i ∑ t = 1 j d [ k ] [ t ] \sum\limits_{i=1}^{x}\sum\limits_{j=1}^{y}{a[i][j]}=\sum\limits_{i=1}^{x}\sum\limits_{j=1}^{y}\sum\limits_{k=1}^{i}\sum\limits_{t=1}^{j}{d[k][t]} i=1xj=1ya[i][j]=i=1xj=1yk=1it=1jd[k][t] = ∑ i = 1 x ∑ j = 1 y ( x + 1 − i ) ( y + 1 − j ) d [ i ] [ j ] =\sum\limits_{i=1}^{x}\sum\limits_{j=1}^{y}{(x+1-i)(y+1-j)d[i][j]} =i=1xj=1y(x+1i)(y+1j)d[i][j]

= ( x + 1 ) ( y + 1 ) ∑ i = 1 x ∑ j = 1 y d [ i ] [ j ] =(x+1)(y+1)\sum\limits_{i=1}^{x}\sum\limits_{j=1}^{y}{d[i][j]} =(x+1)(y+1)i=1xj=1yd[i][j] − ( y + 1 ) ∑ i = 1 x ∑ j = 1 y d [ i ] [ j ] ∗ i -(y+1)\sum\limits_{i=1}^{x}\sum\limits_{j=1}^{y}{d[i][j]*i} (y+1)i=1xj=1yd[i][j]i − ( x + 1 ) ∑ i = 1 x ∑ j = 1 y d [ i ] [ j ] ∗ j -(x+1)\sum\limits_{i=1}^{x}\sum\limits_{j=1}^{y}{d[i][j]*j} (x+1)i=1xj=1yd[i][j]j + ∑ i = 1 x ∑ j = 1 y d [ i ] [ j ] ∗ i j +\sum\limits_{i=1}^{x}\sum\limits_{j=1}^{y}{d[i][j]*ij} +i=1xj=1yd[i][j]ij

用四个树状数组分别维护 d [ i ] [ j ] d[i][j] d[i][j] , d [ i ] [ j ] ∗ i d[i][j]*i d[i][j]i , d [ i ] [ j ] ∗ j d[i][j]*j d[i][j]j , d [ i ] [ j ] ∗ i j d[i][j]*ij d[i][j]ij 的前缀和

const int SIZE=5005;
int limit1=SIZE-1,limit2=SIZE-1;//两个维度的下标的上界
ll bit1[SIZE][SIZE],bit2[SIZE][SIZE],bit3[SIZE][SIZE],bit4[SIZE][SIZE];

void add(int x,int y,ll v){
	for(int i=x;i<=limit1;i+=i&-i)for(int j=y;j<=limit2;j+=j&-j){
		bit1[i][j]+=v;
		bit2[i][j]+=v*x;
		bit3[i][j]+=v*y;
		bit4[i][j]+=v*x*y;
	}
}
ll query(int x,int y){
	ll res=0;
	for(int i=x;i;i-=i&-i)
		for(int j=y;j;j-=j&-j)
			res+=(x+1)*(y+1)*bit1[i][j]-(y+1)*bit2[i][j]-(x+1)*bit3[i][j]+bit4[i][j];
	return res;
}
void real_add(int x1,int y1,int x2,int y2,ll v){
	add(x1,y1,v);
	add(x1,y2+1,-v);
	add(x2+1,y1,-v);
	add(x2+1,y2+1,v);
}
ll real_query(int x1,int y1,int x2,int y2){
	return query(x2,y2)-query(x1-1,y2)-query(x2,y1-1)+query(x1-1,y1-1);
}

线段树

代码一:区间加减+区间求和

const int SIZE=1e6+5;
int LLL=1,RRR=SIZE-5;

ll tree[SIZE<<2],lazy[SIZE<<2];
void build(int l=LLL,int r=RRR,int p=1){
	lazy[p]=0;
	if(l==r){
		tree[p]=0;
		return;
	}
	int mid=l+r>>1;
	build(l,mid,p<<1);
	build(mid+1,r,p<<1|1);
	tree[p]=tree[p<<1]+tree[p<<1|1];
}
void down(int p,int l,int r){
	if(lazy[p]){
		int mid=l+r>>1;
		lazy[p<<1]+=lazy[p];
		lazy[p<<1|1]+=lazy[p];
		tree[p<<1]+=1ll*(mid-l+1)*lazy[p];
		tree[p<<1|1]+=1ll*(r-mid)*lazy[p];
		lazy[p]=0;
	}
}
void update(int a,int b,ll v,int l=LLL,int r=RRR,int p=1){
	if(l>b||r<a)return;
	if(l>=a&&r<=b){
		tree[p]+=1ll*(r-l+1)*v;
		lazy[p]+=v;
		return;
	}
	down(p,l,r);
	int mid=l+r>>1;
	update(a,b,v,l,mid,p<<1);
	update(a,b,v,mid+1,r,p<<1|1);
	tree[p]=tree[p<<1]+tree[p<<1|1];
}
ll query(int a,int b,int l=LLL,int r=RRR,int p=1){
	if(l>b||r<a)return 0;
	if(l>=a&&r<=b)return tree[p];
	down(p,l,r);
	int mid=l+r>>1;
	return query(a,b,l,mid,p<<1)+query(a,b,mid+1,r,p<<1|1);
}

代码二:区间加减+区间最大值

const int SIZE=1e6+5;
int LLL=1,RRR=SIZE-5;

ll tree[SIZE<<2],lazy[SIZE<<2];
void build(int l=LLL,int r=RRR,int p=1){
	lazy[p]=0;
	if(l==r){
		tree[p]=0;
		return;
	}
	int mid=l+r>>1;
	build(l,mid,p<<1);
	build(mid+1,r,p<<1|1);
	tree[p]=max(tree[p<<1],tree[p<<1|1]);
}
void down(int p){
	if(lazy[p]){
		tree[p<<1]+=lazy[p];
		lazy[p<<1]+=lazy[p];
		tree[p<<1|1]+=lazy[p];
		lazy[p<<1|1]+=lazy[p];
		lazy[p]=0;
	}
}
void update(int a,int b,ll v,int l=LLL,int r=RRR,int p=1){
	if(l>b||r<a)return;
	if(l>=a&&r<=b){
		tree[p]+=v;
		lazy[p]+=v;
		return;
	}
	down(p);
	int mid=l+r>>1;
	update(a,b,v,l,mid,p<<1);
	update(a,b,v,mid+1,r,p<<1|1);
	tree[p]=max(tree[p<<1],tree[p<<1|1]);
}
ll query(int a,int b,int l=LLL,int r=RRR,int p=1){
	if(l>b||r<a)return 0;//
	if(l>=a&&r<=b)return tree[p];
	down(p);
	int mid=l+r>>1;
	return max(query(a,b,l,mid,p<<1),query(a,b,mid+1,r,p<<1|1));
}

代码三:查询区间第一个大于等于某个值的位置

int query_pos(int a,int b,int v,int l=LLL,int r=RRR,int p=1){
	if(l>b||r<a)return 0;
	if(l==r)return l;
	down(p);
	int mid=l+r>>1,res=0;
	if(tree[p<<1]>=v)res=query_pos(a,b,v,l,mid,p<<1);
	if(res==0&&tree[p<<1|1]>=v)res=query_pos(a,b,v,mid+1,r,p<<1|1);
	return res;
}

代码四:P3373 区间加、区间乘、区间求和

const int M=1e5+5;
int n,m,mod,A[M],tree[M<<2],lazy_mul[M<<2],lazy_add[M<<2];
void build(int l=1,int r=n,int p=1){
	lazy_mul[p]=1;
	lazy_add[p]=0;
	if(l==r){
		tree[p]=A[l]%mod;
		return;
	}
	int mid=l+r>>1;
	build(l,mid,p<<1);
	build(mid+1,r,p<<1|1);
	tree[p]=(tree[p<<1]+tree[p<<1|1])%mod;
}
void down(int p,int l,int r){
	if(lazy_mul[p]!=1){
		tree[p<<1]=1ll*tree[p<<1]*lazy_mul[p]%mod;
		lazy_mul[p<<1]=1ll*lazy_mul[p<<1]*lazy_mul[p]%mod;
		lazy_add[p<<1]=1ll*lazy_add[p<<1]*lazy_mul[p]%mod;
		tree[p<<1|1]=1ll*tree[p<<1|1]*lazy_mul[p]%mod;
		lazy_mul[p<<1|1]=1ll*lazy_mul[p<<1|1]*lazy_mul[p]%mod;
		lazy_add[p<<1|1]=1ll*lazy_add[p<<1|1]*lazy_mul[p]%mod;
		lazy_mul[p]=1;
	}
	if(lazy_add[p]){
		int mid=l+r>>1;
		tree[p<<1]=(tree[p<<1]+1ll*(mid-l+1)*lazy_add[p])%mod;
		lazy_add[p<<1]=(lazy_add[p<<1]+lazy_add[p])%mod;
		tree[p<<1|1]=(tree[p<<1|1]+1ll*(r-mid)*lazy_add[p])%mod;
		lazy_add[p<<1|1]=(lazy_add[p<<1|1]+lazy_add[p])%mod;
		lazy_add[p]=0;
	}
}
void update(int a,int b,int mul,int add,int l=1,int r=n,int p=1){
	if(l>b||r<a)return;
	if(l>=a&&r<=b){
		if(mul!=1){
			tree[p]=1ll*tree[p]*mul%mod;
			lazy_mul[p]=1ll*lazy_mul[p]*mul%mod;
			lazy_add[p]=1ll*lazy_add[p]*mul%mod;
		}
		if(add){
			tree[p]=(tree[p]+1ll*(r-l+1)*add)%mod;
			lazy_add[p]=(lazy_add[p]+add)%mod;
		}
		return;
	}
	down(p,l,r);
	int mid=l+r>>1;
	update(a,b,mul,add,l,mid,p<<1);
	update(a,b,mul,add,mid+1,r,p<<1|1);
	tree[p]=(tree[p<<1]+tree[p<<1|1])%mod;
}
int query(int a,int b,int l=1,int r=n,int p=1){
	if(l>b||r<a)return 0;
	if(l>=a&&r<=b)return tree[p];
	down(p,l,r);
	int mid=l+r>>1;
	return (query(a,b,l,mid,p<<1)+query(a,b,mid+1,r,p<<1|1))%mod;
}

主席树

静态区间第k小 P3834

const int SIZE=2e5+5;
const int TREE=SIZE*20;
int n,q,uni,A[SIZE],tmp[SIZE];
int trid,rt[SIZE],ls[TREE],rs[TREE],sum[TREE];
void update(int &now,int lst,int x,int l=1,int r=uni){
	now=++trid;
	ls[now]=ls[lst],rs[now]=rs[lst],sum[now]=sum[lst]+1;
	if(l==r)return;
	int mid=l+r>>1;
	if(x<=mid)update(ls[now],ls[lst],x,l,mid);
	else update(rs[now],rs[lst],x,mid+1,r);
}
int query(int left,int right,int k,int l=1,int r=uni){
	if(l==r)return l;
	int mid=l+r>>1,res=sum[ls[right]]-sum[ls[left]];
	if(k<=res)return query(ls[left],ls[right],k,l,mid);
	else return query(rs[left],rs[right],k-res,mid+1,r);
}
int main(){
#ifndef ONLINE_JUDGE
	freopen("jiedai.in","r",stdin);
#endif
	rd(n),rd(q);
	for(int i=1;i<=n;i++)rd(A[i]),tmp[++uni]=A[i];
	sort(tmp+1,tmp+1+uni);
	uni=unique(tmp+1,tmp+1+uni)-tmp-1;
	for(int i=1;i<=n;i++)A[i]=lower_bound(tmp+1,tmp+1+uni,A[i])-tmp;
	for(int i=1;i<=n;i++)update(rt[i],rt[i-1],A[i]);
	while(q--){
		int l,r,k;
		rd(l),rd(r),rd(k);
		printf("%d\n",tmp[query(rt[l-1],rt[r],k)]);
	}
	return (0-0);
}

线段树合并/裂开

线段树合并的复杂度证明:合并两个线段树的复杂度是它们的交集部分,合并时每到达一个点都意味着有两个点变成了一个点,也就是少了一个点。因此线段树无论怎么合并,复杂度都不会超过插入时的新建节点总数,即 O ( n l o g n ) O(nlogn) O(nlogn)

P5494

维护若干可重集合,初始集合数为1,支持以下操作:

0 p x y :将可重集 p 中大于等于 x 且小于等于 y 的值放入一个新的可重集中

1 p t :将可重集 t 中的数并入可重集 p,且删除可重集 t

2 p x q :在可重集 p 中加入 x 个数字 q

3 p x y :查询可重集 p 中大于等于 x 且小于等于 y 的值的个数

4 p k :查询可重集 p 中第 k 小的数,不存在输出 -1

const int SIZE=2e5+5;
const int TREE=SIZE*32;
int LLL=1,RRR=SIZE-5;

int trid,rub,rt[SIZE],bin[TREE],ls[TREE],rs[TREE];
ll cnt[TREE];
int new_node(){
	if(rub)return bin[rub--];
	else return ++trid;
}
void del_node(int p){
	ls[p]=rs[p]=cnt[p]=0;
	bin[++rub]=p;
}
void update(int &p,int x,int v,int l=LLL,int r=RRR){
	if(p==0)p=new_node();
	cnt[p]+=v;
	if(l==r)return;
	int mid=l+r>>1;
	if(x<=mid)update(ls[p],x,v,l,mid);
	else update(rs[p],x,v,mid+1,r);
}
ll query_cnt(int p,int a,int b,int l=LLL,int r=RRR){
	if(p==0||l>b||r<a)return 0;
	if(l>=a&&r<=b)return cnt[p];
	int mid=l+r>>1;
	return query_cnt(ls[p],a,b,l,mid)+query_cnt(rs[p],a,b,mid+1,r);
}
int query_kth(int p,ll k,int l=LLL,int r=RRR){
	if(l==r)return l;
	int mid=l+r>>1;
	if(k<=cnt[ls[p]])return query_kth(ls[p],k,l,mid);
	else return query_kth(rs[p],k-cnt[ls[p]],mid+1,r);
}
int merge(int x,int y){
	if(!x||!y)return x|y;
	cnt[x]+=cnt[y];
	ls[x]=merge(ls[x],ls[y]);
	rs[x]=merge(rs[x],rs[y]);
	del_node(y);
	return x;
}
void split(int x,int &y,ll k){
	if(x==0)return;
	y=new_node();
	ll v=cnt[ls[x]];
	if(k<=v)swap(rs[x],rs[y]);
	if(k<v)split(ls[x],ls[y],k);
	if(k>v)split(rs[x],rs[y],k-v);
	cnt[y]=cnt[x]-k;
	cnt[x]=k;
}

int n,q,all;
int main(){
#ifndef ONLINE_JUDGE
	freopen("jiedai.in","r",stdin);
#endif
	rd(n),rd(q);
	rt[all=1]=new_node();
	for(int i=1;i<=n;i++){
		int x;
		rd(x);
		update(rt[1],i,x);
	}
	while(q--){
		int op,p,x,y;
		ll k;
		rd(op);
		if(op==0){
			rd(p),rd(x),rd(y);
			ll k1=query_cnt(rt[p],1,y);
			ll k2=query_cnt(rt[p],x,y);
			int tmp;
			split(rt[p],rt[++all],k1-k2);
			split(rt[all],tmp,k2);
			rt[p]=merge(rt[p],tmp);
		}
		else if(op==1){
			rd(p),rd(y);
			rt[p]=merge(rt[p],rt[y]);
		}
		else if(op==2){
			rd(p),rd(x),rd(y);
			update(rt[p],y,x);
		}
		else if(op==3){
			rd(p),rd(x),rd(y);
			printf("%lld\n",query_cnt(rt[p],x,y));
		}
		else if(op==4){
			rd(p),rd(k);
			if(k<=0||k>query_cnt(rt[p],1,n))puts("-1");
			else printf("%d\n",query_kth(rt[p],k));
		}
	}
	return (0-0);
}

吉司机线段树

Segment Tree Beats!

BZOJ4695

维护一个序列,支持下面几种操作

1 L R v :给区间[L,R]加上一个数v

2 L R v :把区间[L,R]中小于v的数变成v

3 L R v :把区间[L,R]中大于v的数变成v

4 L R :求区间[L,R]的和

5 L R :求区间[L,R]的最大值

6 L R :求区间[L,R]的最小值

时间复杂度 O ( n l o g 2 n ) O(nlog^2{n}) O(nlog2n)

const int SIZE=5e5+5;
const ll INF=1e18;

int n;
ll A[SIZE];

int len[SIZE<<2],cmx[SIZE<<2],cmi[SIZE<<2];
ll sum[SIZE<<2],mx[SIZE<<2],mx2[SIZE<<2],mi[SIZE<<2],mi2[SIZE<<2];
ll lazy_mx[SIZE<<2],lazy_mi[SIZE<<2],lazy_add[SIZE<<2];
void up(int p){
	int ls=(p<<1),rs=(p<<1|1);
	sum[p]=sum[ls]+sum[rs];
	if(mx[ls]>mx[rs]){
		mx[p]=mx[ls];
		mx2[p]=max(mx2[ls],mx[rs]);
		cmx[p]=cmx[ls];
	}
	else if(mx[ls]<mx[rs]){
		mx[p]=mx[rs];
		mx2[p]=max(mx[ls],mx2[rs]);
		cmx[p]=cmx[rs];
	}
	else{
		mx[p]=mx[ls];
		mx2[p]=max(mx2[ls],mx2[rs]);
		cmx[p]=cmx[ls]+cmx[rs];
	}
	if(mi[ls]<mi[rs]){
		mi[p]=mi[ls];
		mi2[p]=min(mi2[ls],mi[rs]);
		cmi[p]=cmi[ls];
	}
	else if(mi[ls]>mi[rs]){
		mi[p]=mi[rs];
		mi2[p]=min(mi[ls],mi2[rs]);
		cmi[p]=cmi[rs];
	}
	else{
		mi[p]=mi[ls];
		mi2[p]=min(mi2[ls],mi2[rs]);
		cmi[p]=cmi[ls]+cmi[rs];
	}
}
void work_max(int p,ll v){
	lazy_mx[p]+=v;
	if(mx[p]==mi[p])mi[p]+=v;
	else if(cmx[p]+cmi[p]==len[p])mi2[p]+=v;
	mx[p]+=v;
	sum[p]+=1ll*v*cmx[p];
}
void work_min(int p,ll v){
	lazy_mi[p]+=v;
	if(mi[p]==mx[p])mx[p]+=v;
	else if(cmi[p]+cmx[p]==len[p])mx2[p]+=v;
	mi[p]+=v;
	sum[p]+=1ll*v*cmi[p];
}
void work_add(int p,ll v){
	lazy_add[p]+=v;
	mx[p]+=v;
	mi[p]+=v;
	if(mx2[p]!=-INF)mx2[p]+=v;
	if(mi2[p]!=INF)mi2[p]+=v;
	sum[p]+=1ll*v*len[p];
}
void down(int p){
	int ls=(p<<1),rs=(p<<1|1);
	if(lazy_mx[p]){
		if(mx[ls]>mx[rs])work_max(ls,lazy_mx[p]);
		else if(mx[ls]<mx[rs])work_max(rs,lazy_mx[p]);
		else work_max(ls,lazy_mx[p]),work_max(rs,lazy_mx[p]);
		lazy_mx[p]=0;
	}
	if(lazy_mi[p]){
		if(mi[ls]<mi[rs])work_min(ls,lazy_mi[p]);
		else if(mi[ls]>mi[rs])work_min(rs,lazy_mi[p]);
		else work_min(ls,lazy_mi[p]),work_min(rs,lazy_mi[p]);
		lazy_mi[p]=0;
	}
	if(lazy_add[p]){
		work_add(ls,lazy_add[p]);
		work_add(rs,lazy_add[p]);
		lazy_add[p]=0;
	}
}
void build(int l=1,int r=n,int p=1){
	len[p]=r-l+1;
	lazy_mx[p]=lazy_mi[p]=lazy_add[p]=0;
	if(l==r){
		mx[p]=mi[p]=sum[p]=A[l];
		mx2[p]=-INF,mi2[p]=INF;
		cmx[p]=cmi[p]=1;
		return;
	}
	int mid=l+r>>1;
	build(l,mid,p<<1);
	build(mid+1,r,p<<1|1);
	up(p);
}
void update_max(int a,int b,ll v,int l=1,int r=n,int p=1){
	if(l>b||r<a)return;
	if(l>=a&&r<=b){
		if(v<=mi[p])return;
		else if(v<mi2[p]){
			work_min(p,v-mi[p]);
			return;
		}
	}
	down(p);
	int mid=l+r>>1;
	update_max(a,b,v,l,mid,p<<1);
	update_max(a,b,v,mid+1,r,p<<1|1);
	up(p);
}
void update_min(int a,int b,ll v,int l=1,int r=n,int p=1){
	if(l>b||r<a)return;
	if(l>=a&&r<=b){
		if(v>=mx[p])return;
		else if(v>mx2[p]){
			work_max(p,v-mx[p]);
			return;
		}
	}
	down(p);
	int mid=l+r>>1;
	update_min(a,b,v,l,mid,p<<1);
	update_min(a,b,v,mid+1,r,p<<1|1);
	up(p);
}
void update_add(int a,int b,ll v,int l=1,int r=n,int p=1){
	if(l>b||r<a)return;
	if(l>=a&&r<=b){
		work_add(p,v);
		return;
	}
	down(p);
	int mid=l+r>>1;
	update_add(a,b,v,l,mid,p<<1);
	update_add(a,b,v,mid+1,r,p<<1|1);
	up(p);
}
ll query_max(int a,int b,int l=1,int r=n,int p=1){
	if(l>b||r<a)return -INF;
	if(l>=a&&r<=b)return mx[p];
	down(p);
	int mid=l+r>>1;
	return max(query_max(a,b,l,mid,p<<1),query_max(a,b,mid+1,r,p<<1|1));
}
ll query_min(int a,int b,int l=1,int r=n,int p=1){
	if(l>b||r<a)return INF;
	if(l>=a&&r<=b)return mi[p];
	down(p);
	int mid=l+r>>1;
	return min(query_min(a,b,l,mid,p<<1),query_min(a,b,mid+1,r,p<<1|1));
}
ll query_sum(int a,int b,int l=1,int r=n,int p=1){
	if(l>b||r<a)return 0;
	if(l>=a&&r<=b)return sum[p];
	down(p);
	int mid=l+r>>1;
	return query_sum(a,b,l,mid,p<<1)+query_sum(a,b,mid+1,r,p<<1|1);	
}
int main(){
#ifndef ONLINE_JUDGE
	freopen("jiedai.in","r",stdin);
#endif
	int q,op,l,r;
	ll v;
	rd(n);
	for(int i=1;i<=n;i++)rd(A[i]);
	build();
	rd(q);
	while(q--){
		rd(op),rd(l),rd(r);
		if(0-0);
		else if(op==1)rd(v),update_add(l,r,v);
		else if(op==2)rd(v),update_max(l,r,v);
		else if(op==3)rd(v),update_min(l,r,v);
		else if(op==4)printf("%lld\n",query_sum(l,r));
		else if(op==5)printf("%lld\n",query_max(l,r));
		else if(op==6)printf("%lld\n",query_min(l,r));
	}
	return (0-0);
}

李超线段树*

可撤销并查集

struct disjoint_set{
	static const int SIZE=1e6+5;
	int top,stk[SIZE],fa[SIZE],sz[SIZE];
	inline void init(int n){
		top=0;
		for(int i=1;i<=n;i++)fa[i]=i,sz[i]=1;
	}
	inline int getfa(int x){
		while(x!=fa[x])x=fa[x];
		return x;
	}
	inline bool link(int x,int y){
		x=getfa(x),y=getfa(y);
		if(x!=y){
			if(sz[x]>sz[y])swap(x,y);
			stk[++top]=x;
			fa[x]=y;
			sz[y]+=sz[x];
			return 1;
		}
		else return 0;
	}
	inline void pop_to(int tar){
		while(top>tar){
			sz[fa[stk[top]]]-=sz[stk[top]];
			fa[stk[top]]=stk[top];
			top--;
		}
	}
}U;

ST表

静态区间最大值 P3865

const int M=1e5+5;
int n,q,A[M],st[M][20];
inline int ST(int l,int r){
	int k=log2(r-l+1);
	return max(st[l][k],st[r-(1<<k)+1][k]);
}
int main(){
#ifndef ONLINE_JUDGE
	freopen("jiedai.in","r",stdin);
#endif
	rd(n),rd(q);
	for(int i=1;i<=n;i++)rd(A[i]);
	for(int i=1;i<=n;i++)st[i][0]=A[i];
	for(int i=n;i>=1;i--)
		for(int j=1;i+(1<<j)-1<=n;j++)
			st[i][j]=max(st[i][j-1],st[i+(1<<j-1)][j-1]);
	while(q--){
		int l,r;
		rd(l),rd(r);
		printf("%d\n",ST(l,r));
	}
	return (0-0);
}

分块

代码一: O ( ( n ) ) O(\sqrt(n)) O(( n))单点修改 + O ( 1 ) O(1) O(1)区间查询

struct BLOCK1{
    static const int M=2e5+5;
	static const int S=400;
	static const int K=M/S+5;
	int bel[M],L[K],R[K];
	ll big[K],small[M];
	BLOCK1(){
		for(int i=1;i<M;i++)bel[i]=(i-1)/S+1;
		for(int i=1;i<K;i++)L[i]=1+(i-1)*S,R[i]=i*S;
	}
	void update(int x,ll v){
		for(int i=1;i<=bel[x];i++)big[i]+=v;
		for(int i=L[bel[x]];i<=x;i++)small[i]+=v;
	}
	ll query(int l,int r){
		return big[bel[l]+1]+small[l]-big[bel[r+1]+1]-small[r+1];
	}
}block1;

代码二: O ( 1 ) O(1) O(1)单点修改 + O ( ( n ) ) O(\sqrt(n)) O(( n))区间查询

struct BLOCK2{
    static const int M=2e5+5;
	static const int S=400;
	static const int K=M/S+5;
	int bel[M],L[K],R[K];
	ll big[K],small[M];
	BLOCK2(){
		for(int i=1;i<M;i++)bel[i]=(i-1)/S+1;
		for(int i=1;i<K;i++)L[i]=1+(i-1)*S,R[i]=i*S;
	}
	void update(int x,ll v){
		big[bel[x]]+=v;
		small[x]+=v;
	}
	ll query(int l,int r){
		ll res=0;
		if(bel[l]!=bel[r]){
			for(int i=l;i<=R[bel[l]];i++)res+=small[i];
			for(int i=bel[l]+1;i<bel[r];i++)res+=big[i];
			for(int i=L[bel[r]];i<=r;i++)res+=small[i];
		}
		else for(int i=l;i<=r;i++)res+=small[i];
		return res;
	}
}block2;

代码三: O ( ( n ) ) O(\sqrt(n)) O(( n))区间修改 + O ( 1 ) O(1) O(1)单点查询

struct BLOCK3{
    static const int M=2e5+5;
	static const int S=400;
	static const int K=M/S+5;
	int bel[M],L[K],R[K];
	ll big[K],small[M];
	BLOCK3(){
		for(int i=1;i<M;i++)bel[i]=(i-1)/S+1;
		for(int i=1;i<K;i++)L[i]=1+(i-1)*S,R[i]=i*S;
	}
	void update(int l,int r,ll v){
		if(bel[l]!=bel[r]){
			for(int i=l;i<=R[bel[l]];i++)small[i]+=v;
			for(int i=bel[l]+1;i<bel[r];i++)big[i]+=v;
			for(int i=L[bel[r]];i<=r;i++)small[i]+=v;
		}
		else for(int i=l;i<=r;i++)small[i]+=v;
	}
	ll query(int x){
		return big[bel[x]]+small[x];
	}
}block3;

代码四: O ( 1 ) O(1) O(1)区间修改 + O ( ( n ) ) O(\sqrt(n)) O(( n))单点查询

struct BLOCK4{
    static const int M=2e5+5;
	static const int S=400;
	static const int K=M/S+5;
	int bel[M],L[K],R[K];
	ll big[K],small[M];
	BLOCK4(){
		for(int i=1;i<M;i++)bel[i]=(i-1)/S+1;
		for(int i=1;i<K;i++)L[i]=1+(i-1)*S,R[i]=i*S;
	}
	void update(int l,int r,ll v){
		big[bel[l]]+=v,small[l]+=v;
		big[bel[r+1]]-=v,small[r+1]-=v;
	}
	ll query(int x){
		ll res=0;
		for(int i=0;i<bel[x];i++)res+=big[i];
		for(int i=L[bel[x]];i<=x;i++)res+=small[i];
		return res;
	}
}block4;

莫队

普通莫队

给定长度为n的序列,m次询问,求区间 [ l , r ] [l,r] [l,r] 之间有多少种不同的数字 P1972

const int M=1e6+5;
const int S=sqrt(M);
struct node{
	int l,r,id;
	bool operator <(const node &A)const{
		if(l/S!=A.l/S)return l/S<A.l/S;
		if(l/S&1)return r<A.r;
		else return r>A.r;
	}
}Q[M];
int n,m,res,A[M],ans[M],cnt[M];
void insert(int x){
	cnt[x]++;
	if(cnt[x]==1)res++;
}
void erase(int x){
	cnt[x]--;
	if(cnt[x]==0)res--;
}
int main(){
#ifndef ONLINE_JUDGE
	freopen("jiedai.in","r",stdin);
#endif
	rd(n);
	for(int i=1;i<=n;i++)rd(A[i]);
	rd(m);
	for(int i=1;i<=m;i++)rd(Q[i].l),rd(Q[i].r),Q[i].id=i;
	sort(Q+1,Q+1+m);
	int l=1,r=0;
	for(int i=1;i<=m;i++){
		while(r<Q[i].r)insert(A[++r]);
		while(l>Q[i].l)insert(A[--l]);
		while(r>Q[i].r)erase(A[r--]);
		while(l<Q[i].l)erase(A[l++]);
		ans[Q[i].id]=res;
	}
	for(int i=1;i<=m;i++)printf("%d\n",ans[i]);
	return (0-0);
}

带修莫队

  1. 修改某个位置的数字

  2. 求区间 [ l , r ] [l,r] [l,r] 之间有多少种不同的数字

这是一道带修莫队的模板题。

每个查询操作除了有左端点l和右端点r,还有一个时间t,表示这个查询在第t个修改操作之后。

将这些查询(l,r,t)进行排序,以l/S为第一关键字,r/S为第二关键字,t为第三关键字。其中S为分块的大小。

与普通莫队相比,当从上一个询问跳到当前的询问时,还要维护目前的修改数now。

当now小于查询的修改数时,就把没有进行的修改操作执行。

当now大于查询的修改数时,就将多出的已执行的修改进行回撤。

需要注意的是,如果执行或回撤的修改操作在目前的区间内,就要更新答案。

在修改数now与查询的修改数相等之后,再进行左右端点的移动。

复杂度分析:n为序列长度,c为修改操作数,q为查询操作数,S为分块的大小。

  1. 修改数的移动:

左端点分成n/S块,右端点分成n/S块,每个左右端点块最多移动c。因此移动次数为 O ( c ∗ n 2 / S 2 ) O(c*n^2/S^2) O(cn2/S2)

  1. 左端点的移动:

在同一左端点块中,每次最多移动S;到下一左端点块时,最多移动2S。因此移动次数为 O ( q S ) O(qS) O(qS)

  1. 右端点的移动:

在同一右端点块中,每次最多移动S;到下一右端点块时,每次最多移动2S;

当左端点改变时,每次最多移动n。因此移动次数为 O ( q S + n 2 / S ) O(qS+n^2/S) O(qS+n2/S)

于是总移动次数为 O ( q S + c ∗ n 2 / S 2 + n 2 / S ) O(qS+c*n^2/S^2+n^2/S) O(qS+cn2/S2+n2/S)

由于不知道c和q的值,那么c和q统一用m代替,且假设n=m,那么总移动次数就成了 O ( q S + n 3 / S 2 + n 2 / S ) O(qS+n^3/S^2+n^2/S) O(qS+n3/S2+n2/S)

S = n 2 / 3 S=n^{2/3} S=n2/3时,最优复杂度为 n 5 / 3 n^{5/3} n5/3

const int M=140005;
const int S=2500;
const int K=1e6+5;
struct node{
	int l,r,t,id;
	bool operator <(const node &A)const{
		if(l/S!=A.l/S)return l/S<A.l/S;
		if(r/S!=A.r/S){
			if(l/S&1)return r/S<A.r/S;
			else return r/S>A.r/S;
		}
		if(r/S&1)return t<A.t;
		else return t>A.t;
	}
}Q[M];
char str[M][5];
int n,m,A[M],pos[M],X[M],Y[M],ans[M];
int res,cnt[K];
void insert(int x){
	cnt[x]++;
	if(cnt[x]==1)res++;
}
void erase(int x){
	cnt[x]--;
	if(cnt[x]==0)res--;
}
int main(){
#ifndef ONLINE_JUDGE
	freopen("jiedai.in","r",stdin);
#endif
	rd(n),rd(m);
	for(int i=1;i<=n;i++)rd(A[i]);
	int l=1,r=0,now=0,tot=0;
	for(int i=1;i<=m;i++){
		scanf("%s",str[i]);
		int a,b;
		rd(a),rd(b);
		if(str[i][0]=='R'){
			now++;
			pos[now]=a;
			X[now]=A[a];
			Y[now]=b;
			A[a]=b;
		}
		else Q[++tot]=(node){a,b,now,i};
	}
	sort(Q+1,Q+1+tot);
	for(int i=1;i<=tot;i++){
		while(now<Q[i].t){
			now++;
			A[pos[now]]=Y[now];
			if(pos[now]>=l&&pos[now]<=r)erase(X[now]),insert(Y[now]);
		}
		while(now>Q[i].t){
			A[pos[now]]=X[now];
			if(pos[now]>=l&&pos[now]<=r)erase(Y[now]),insert(X[now]);
			now--;
		}
		while(r<Q[i].r)insert(A[++r]);
		while(l>Q[i].l)insert(A[--l]);
		while(r>Q[i].r)erase(A[r--]);
		while(l<Q[i].l)erase(A[l++]);
		ans[Q[i].id]=res;
	}
	for(int i=1;i<=m;i++)if(str[i][0]=='Q')printf("%d\n",ans[i]);
	return (0-0);
}

树上带修莫队

n n n个节点的树, m m m种颜色, q q q个询问。
树上每个节点有一个颜色,每种颜色有一个权值。
i i i次遍历到某种颜色的点,该点的价值乘上对应的数值。
0 0 0 x x x y y y : 把 x x x点的颜色改为 y y y
1 1 1 x x x y y y : 查询遍历 x x x y y y路径的总价值。

思路:用树的括号序将查询的一条链转换成括号序列上的一个区间,再在括号序列上利用带修莫队求解。

将链转换成区间

处理出树的括号序,在对树进行 d f s dfs dfs 的时候,进入和离开某个点的时候都要打上标记,记 x x x点进入和离开的时间戳为 i d L [ x ] idL[x] idL[x] i d R [ x ] idR[x] idR[x]
将一条链转换成一个括号区间,要保证链上的点在区间中只出现一个括号,不在链上的点在区间上没有出现,或者出现的两个括号相互抵消。

对于一条端点为 x x x y y y 的链,假设 i d L [ x ] ≤ i d L [ y ] idL[x]≤idL[y] idL[x]idL[y] ,算出 x 和 y 的 L C A LCA LCA

  1. 如果 x x x y y y 就是 l c a lca lca ,那么这条链对应的括号序区间为 i d L [ x ] idL[x] idL[x] i d L [ y ] idL[y] idL[y]
  2. 如果 x x x y y y 都不是 l c a lca lca ,那么对应的区间为 i d R [ x ] idR[x] idR[x] i d L [ y ] idL[y] idL[y]。不过这个区间中并没有包含 l c a lca lca 这个点,这需要在处理询问的时候特别加上。

带修莫队

莫队的区间中第二次加入一个点时,改为删除该点,撤销该操作就是加入该点,可以标记该点的奇偶性来进行判断。
加上修改操作后同理,利用奇偶性判断是否修改。

const int M=1e5+5;
const int S=2000;
int n,m,q,A[M],B[M],C[M],tot,head[M],to[M<<1],nxt[M<<1];
inline void add_edge(int a,int b){
	to[++tot]=b;
	nxt[tot]=head[a];
	head[a]=tot;
}
int fa[M],son[M],sz[M],deep[M],top[M];
int reid[M<<1],idL[M],idR[M],dfsid;
void dfs(int x,int f){
	fa[x]=f;
	sz[x]=1;
	deep[x]=deep[f]+1;
	reid[idL[x]=++dfsid]=x;
	for(int i=head[x];i;i=nxt[i]){
		int y=to[i];
		if(y==f)continue;
		dfs(y,x);
		sz[x]+=sz[y];
		if(sz[y]>sz[son[x]])son[x]=y;
	}
	reid[idR[x]=++dfsid]=x;
}
void chain(int x,int k){
	top[x]=k;
	if(son[x])chain(son[x],k);
	for(int i=head[x];i;i=nxt[i]){
		int y=to[i];
		if(y==fa[x]||y==son[x])continue;
		chain(y,y);
	}
}
int LCA(int a,int b){
	while(top[a]!=top[b]){
		if(deep[top[a]]<deep[top[b]])swap(a,b);
		a=fa[top[a]];
	}
	return deep[a]<deep[b]?a:b;
}
struct node{
	int l,r,t,lca,id;
	bool operator <(const node &A)const{
		if(l/S!=A.l/S)return l/S<A.l/S;
		if(r/S!=A.r/S){
			if(l/S%2)return r/S<A.r/S;
			else return r/S>A.r/S;
		}
		if(r/S%2)return t<A.t;
		else return t>A.t;
	}
}Q[M];
int op0,op1,X[M],Y[M],Z[M],mark[M],cnt[M];
ll res,ans[M];
void add(int x){
	mark[x]^=1;
	if(mark[x])res+=1ll*A[C[x]]*B[++cnt[C[x]]];
	else res-=1ll*A[C[x]]*B[cnt[C[x]]--];
}
int main(){
#ifndef ONLINE_JUDGE
	freopen("jiedai.in","r",stdin);
#endif
	rd(n),rd(m),rd(q);
	for(int i=1;i<=m;i++)rd(A[i]);
	for(int i=1;i<=n;i++)rd(B[i]);
	for(int i=1;i<n;i++){
		int a,b;
		rd(a),rd(b);
		add_edge(a,b),add_edge(b,a);
	}
	for(int i=1;i<=n;i++)rd(C[i]);
	dfs(1,0);
	chain(1,1);
	int now=0;
	for(int i=1;i<=q;i++){
		int op,x,y;
		rd(op),rd(x),rd(y);
		if(op==0){
			X[++op0]=x,Y[op0]=y,Z[op0]=C[x];
			C[x]=y,now++;
		}
		else{
			int lca=LCA(x,y);
			if(idL[x]>idL[y])swap(x,y);
			if(x==lca||y==lca)Q[++op1]=(node){idL[x],idL[y],op0,0,i};
			else Q[++op1]=(node){idR[x],idL[y],op0,lca,i};
		}
	}
	sort(Q+1,Q+1+op1);
	int L=Q[1].l,R=L-1;
	for(int i=1;i<=op1;i++){
		int l=Q[i].l,r=Q[i].r,t=Q[i].t,lca=Q[i].lca,id=Q[i].id;
		while(L>l)add(reid[--L]);
		while(R<r)add(reid[++R]);
		while(L<l)add(reid[L++]);
		while(R>r)add(reid[R--]);
		while(now<t){
			now++;
			if(mark[X[now]]){
				add(X[now]);
				C[X[now]]=Y[now];
				add(X[now]);
			}
			else C[X[now]]=Y[now];
		}
		while(now>t){
			if(mark[X[now]]){
				add(X[now]);
				C[X[now]]=Z[now];
				add(X[now]);
			}
			else C[X[now]]=Z[now];
			now--;
		}
		if(lca)add(lca);
		ans[id]=res;
		if(lca)add(lca);
	}
	for(int i=1;i<=q;i++)if(ans[i])printf("%lld\n",ans[i]);
	return (0-0);
}

回滚莫队

loj 2874

[ l , r ] [l,r] [l,r]中一种数权值与出现次数之积的最大值

const int M=2e5+5;
const int S=350;
struct node{
	int l,r,id;
	bool operator <(const node &A)const{
		if(l/S!=A.l/S)return l/S<A.l/S;
		return r<A.r;
	}
}Q[M];
int n,m,A[M],B[M],cnt[M],cnt2[M];
ll res,ans[M];

int main(){
#ifndef ONLINE_JUDGE
	freopen("jiedai.in","r",stdin);
#endif
	rd(n),rd(m);
	for(int i=1;i<=n;i++)rd(A[i]),B[i]=A[i];
	sort(B+1,B+1+n);
	int uni=unique(B+1,B+1+n)-B-1;
	for(int i=1;i<=n;i++)A[i]=lower_bound(B+1,B+1+uni,A[i])-B;
	for(int i=1;i<=m;i++){
		int l,r;
		rd(l),rd(r);
		Q[i]=(node){l,r,i};
	}
	sort(Q+1,Q+1+m);
	int L=0,R=0;
	for(int i=1;i<=m;i++){
		if(i==1||Q[i].l/S!=Q[i-1].l/S){
			L=min(n+1,Q[i].l/S*S+S);
			R=L-1;
			res=0;
			for(int j=1;j<=uni;j++)cnt[j]=0;
		}
		int l=Q[i].l,r=Q[i].r,id=Q[i].id;
		if(l/S==r/S){
			ll tmp=0;
			for(int j=l;j<=r;j++)MAX(tmp,1ll*B[A[j]]*(++cnt2[A[j]]));
			for(int j=l;j<=r;j++)cnt2[A[j]]=0;
			ans[id]=tmp;
		}
		else{
			while(R<r)++R,MAX(res,1ll*B[A[R]]*(++cnt[A[R]]));
			ll tmp=res;
			for(int j=l;j<L;j++)MAX(tmp,1ll*B[A[j]]*(++cnt[A[j]]));
			for(int j=l;j<L;j++)cnt[A[j]]--;
			ans[id]=tmp;
		}
	}
	for(int i=1;i<=m;i++)printf("%lld\n",ans[i]);
	return (0-0);
}

二次离线莫队

二次离线莫队算法可以计算区间中满足某种条件的点对的数量,例如P5047

题意:给定一个长度为n的序列 m次查询 求区间[L,R]中逆序对的数量

分析:如果直接用普通莫队做这道题的话,每次移动都要在树状数组上修改和查询,复杂度为 O ( n n l o g n ) O(n\sqrt{n}logn) O(nn logn)

( [ l 1 , r 1 ] , [ l 2 , r 2 ] ) ([l1,r1],[l2,r2]) ([l1,r1],[l2,r2]) 表示一个点在 [ l 1 , r 1 ] [l1,r1] [l1,r1] 中另外一点在 [ l 2 , r 2 ] [l2,r2] [l2,r2] 中的逆序对数

在做莫队的过程中,假如要将区间 [ l , r ] [l,r] [l,r] ,通过右端点右移,变成区间 [ l , r ′ ] [l,r'] [l,r] 。那么一次从 [ l , i − 1 ] [l,i-1] [l,i1] 变到 [ l , i ] [l,i] [l,i] 的移动,要让当前维护的答案加上 ( [ l , i − 1 ] , [ i , i ] ) ([l,i-1],[i,i]) ([l,i1],[i,i]) ,转化一下也就是 ( [ 1 , i − 1 ] , [ i , i ] ) − ( [ 1 , l − 1 ] , [ i , i ] ) ([1,i-1],[i,i])-([1,l-1],[i,i]) ([1,i1],[i,i])([1,l1],[i,i])

因此在所有移动之后,当前答案应该加上: ∑ i = r + 1 r ′ ( [ 1 , i − 1 ] , [ i , i ] ) − ( [ 1 , l − 1 ] , [ r + 1 , r ′ ] ) \sum\limits_{i=r+1}^{r'}{([1,i-1],[i,i])}-([1,l-1],[r+1,r']) i=r+1r([1,i1],[i,i])([1,l1],[r+1,r])

前者只有n种情况,用树状数组 O ( n l o g n ) O(nlogn) O(nlogn) 进行预处理,然后利用前缀和进行计算

后者是一个前缀和一个区间的形式,我们可以离线将每个区间保存在其前缀上,那么可以从左到右处理每一个前缀以及挂在这个前缀上的区间 [ r + 1 , r ′ ] [r+1,r'] [r+1,r]

所有 [ r + 1 , r ′ ] [r+1,r'] [r+1,r] 的总长,由于莫队的性质,是 O ( n n ) O(n\sqrt{n}) O(nn ) 级别的,而前缀的数量是 O ( n ) O(n) O(n) 的,我们可以用一个 O ( n ) O(\sqrt{n}) O(n ) 单点修改 O ( 1 ) O(1) O(1) 查询的分块。每次前缀加入一个新数,就在容器中 O ( n ) O(\sqrt{n}) O(n ) 进行修改。查询一个位置的元素与前缀形成的逆序对数是 O ( 1 ) O(1) O(1)

总复杂度就是 O ( n l o g n + n n ) O(nlogn+n\sqrt{n}) O(nlogn+nn )

对于左端点左移,左端点右移,右端点左移也类似,要注意的是左端点移动要用后缀

const int M=1e5+5;
const int S=sqrt(M);
const int K=M/S+5;
struct BLOCK{
	int bel[M],L[K],R[K],big[K],small[M];
	BLOCK(){
		for(int i=1;i<M;i++)bel[i]=(i-1)/S+1;
		for(int i=1;i<K;i++)L[i]=1+(i-1)*S,R[i]=i*S;
	}
	void clear(){
		memset(big,0,sizeof(big));
		memset(small,0,sizeof(small));
	}
	void update(int x,int v){
		for(int i=1;i<=bel[x];i++)big[i]+=v;
		for(int i=L[bel[x]];i<=x;i++)small[i]+=v;
	}
	int query(int l,int r){
		return big[bel[l]+1]+small[l]-big[bel[r+1]+1]-small[r+1];
	}
}block;
struct node{
	int l,r,id;
	bool operator <(const node &A)const{
		if(l/S!=A.l/S)return l/S<A.l/S;
		if(l/S&1)return r<A.r;
		else return r>A.r;
	}
}Q[M];
int n,m,uni,A[M],tmp[M],bit[M];
ll ans[M],sum1[M],sum2[M],res1[M],res2[M];
vector<node>vi1[M],vi2[M];
void add(int x,int v){
	while(x<=uni)bit[x]+=v,x+=x&-x;
}
int query(int x){
	int res=0;
	while(x)res+=bit[x],x-=x&-x;
	return res;
}
int main(){
#ifndef ONLINE_JUDGE
	freopen("jiedai.in","r",stdin);
#endif
	rd(n),rd(m);
	for(int i=1;i<=n;i++)rd(A[i]),tmp[i]=A[i];
	for(int i=1;i<=m;i++)rd(Q[i].l),rd(Q[i].r),Q[i].id=i;
	sort(tmp+1,tmp+1+n);
	uni=unique(tmp+1,tmp+1+n)-tmp-1;
	for(int i=1;i<=n;i++)A[i]=lower_bound(tmp+1,tmp+1+uni,A[i])-tmp;
	sort(Q+1,Q+1+m);
	for(int i=1,l=1,r=0;i<=m;i++){
		if(Q[i].r>r)vi1[l-1].push_back((node){r+1,Q[i].r,i}),r=Q[i].r;
		if(Q[i].l<l)vi2[r+1].push_back((node){Q[i].l,l-1,i}),l=Q[i].l;
		if(Q[i].r<r)vi1[l-1].push_back((node){Q[i].r+1,r,i}),r=Q[i].r;
		if(Q[i].l>l)vi2[r+1].push_back((node){l,Q[i].l-1,i}),l=Q[i].l;
	}
	memset(bit,0,sizeof(bit));
	for(int i=1;i<=n;i++){
		sum1[i]=sum1[i-1]+i-1-query(A[i]);
		add(A[i],1);
	}
	memset(bit,0,sizeof(bit));
	for(int i=n;i>=1;i--){
		sum2[i]=sum2[i+1]+query(A[i]-1);
		add(A[i],1);
	}
	block.clear();
	for(int i=0;i<n;i++){
		for(int j=0;j<vi1[i].size();j++){
			int l=vi1[i][j].l,r=vi1[i][j].r,id=vi1[i][j].id;
			for(int k=l;k<=r;k++)res1[id]+=block.query(A[k]+1,uni);
		}
		block.update(A[i+1],1);
	}
	block.clear();
	for(int i=n+1;i>1;i--){
		for(int j=0;j<vi2[i].size();j++){
			int l=vi2[i][j].l,r=vi2[i][j].r,id=vi2[i][j].id;
			for(int k=l;k<=r;k++)res2[id]+=block.query(1,A[k]-1);
		}
		block.update(A[i-1],1);
	}
	ll res=0;
	for(int i=1,l=1,r=0;i<=m;i++){
		if(Q[i].r>r)res+=sum1[Q[i].r]-sum1[r]-res1[i],r=Q[i].r;
		if(Q[i].l<l)res+=sum2[Q[i].l]-sum2[l]-res2[i],l=Q[i].l;
		if(Q[i].r<r)res-=sum1[r]-sum1[Q[i].r]-res1[i],r=Q[i].r;
		if(Q[i].l>l)res-=sum2[l]-sum2[Q[i].l]-res2[i],l=Q[i].l;
		ans[Q[i].id]=res;
	}
	for(int i=1;i<=m;i++)printf("%lld\n",ans[i]);
	return (0-0);
}

线段树分治

P5787

题意:n个节点的图中,在k时间内会有m条边先出现后消失,问每一时刻这个图是否是二分图

int ans=0;
struct disjoint_set{
	static const int SIZE=1e6+5;
	int top,stk[SIZE],fa[SIZE],sz[SIZE];
	int len[SIZE],dis[SIZE];
	void init(int n){
		top=0;
		for(int i=1;i<=n;i++)fa[i]=i,sz[i]=1;
	}
	int getfa(int x){
		if(fa[x]==x){dis[x]=0;return x;}
		int f=getfa(fa[x]);
		dis[x]=dis[fa[x]]+len[x];
		return f;
	}
	void link(int x,int y){
		int fx=getfa(x),fy=getfa(y);
		if(fx!=fy){
			if(sz[fx]>sz[fy])swap(x,y),swap(fx,fy);
			stk[++top]=fx;
			fa[fx]=fy;
			sz[fy]+=sz[fx];
			len[fx]=(dis[x]+dis[y]+1)%2;
		}
		else if(dis[x]%2==dis[y]%2)ans=0;
	}
	void pop_to(int tar){
		while(top>tar){
			sz[fa[stk[top]]]-=sz[stk[top]];
			fa[stk[top]]=stk[top];
			top--;
		}
	}
}U;

const int M=1e5+5;
int n,m,k;
vector<pair<int,int> >vi[M<<2];
void update(int a,int b,int x,int y,int l=1,int r=k,int p=1){
	if(l>b||r<a)return;
	if(l>=a&&r<=b){
		vi[p].push_back(make_pair(x,y));
		return;
	}
	int mid=l+r>>1;
	update(a,b,x,y,l,mid,p<<1);
	update(a,b,x,y,mid+1,r,p<<1|1);
}
void visit(int l=1,int r=k,int p=1){
	int lst_top=U.top,lst_ans=ans;
	for(int i=0;i<vi[p].size();i++)U.link(vi[p][i].first,vi[p][i].second);
	if(l==r)puts(ans?"Yes":"No");
	else{
		int mid=l+r>>1;
		visit(l,mid,p<<1);
		visit(mid+1,r,p<<1|1);
	}
	U.pop_to(lst_top);
	ans=lst_ans;
}
int main(){
#ifndef ONLINE_JUDGE
	freopen("jiedai.in","r",stdin);
#endif
	rd(n),rd(m),rd(k);
	for(int i=1;i<=m;i++){
		int x,y,l,r;
		rd(x),rd(y),rd(l),rd(r);
		if(l<r)update(l+1,r,x,y);
	}
	ans=1;
	U.init(n);
	visit();
	return (0-0);
}

CDQ分治

P3810

题意: n n n 个三元组 ( a , b , c ) (a,b,c) (a,b,c) f ( i ) f(i) f(i) 表示三维都小于等于第 i i i 个三元组的数量,对于每个 d d d ,求 f ( i ) = d f(i)=d f(i)=d 的数量

对于完全相同的三元组,需要进行去重,记录相同三元组的数量

三维偏序

const int M=1e5+5;
int n,k,X[M],Y[M],Z[M],rk[M],cnt[M],res[M],ans[M];
bool cmp(int a,int b){
	if(X[a]!=X[b])return X[a]<X[b];
	if(Y[a]!=Y[b])return Y[a]<Y[b];
	return Z[a]<Z[b];
}
struct node{
	int y,z,id;
	bool operator <(const node &A)const{
		if(y!=A.y)return y<A.y;
		return id<A.id;
	}
}Q[M];
int bit[M<<1];
void add(int x,int v){
	while(x<=k)bit[x]+=v,x+=x&-x;
}
int query(int x){
	int res=0;
	while(x)res+=bit[x],x-=x&-x;
	return res;
}
void CDQ(int l,int r){
	if(l==r)return;
	int mid=l+r>>1;
	CDQ(l,mid),CDQ(mid+1,r);
	sort(Q+l,Q+r+1);//可以用归并排序代替
	for(int i=l;i<=r;i++){
		if(Q[i].id<=mid)add(Q[i].z,cnt[Q[i].id]);
		else res[Q[i].id]+=query(Q[i].z);
	}
	for(int i=l;i<=r;i++)if(Q[i].id<=mid)add(Q[i].z,-cnt[Q[i].id]);
}
int main(){
#ifndef ONLINE_JUDGE
	freopen("jiedai.in","r",stdin);
#endif
	rd(n),rd(k);
	for(int i=1;i<=n;i++)rd(X[i]),rd(Y[i]),rd(Z[i]),rk[i]=i;
	sort(rk+1,rk+1+n,cmp);
	int uni=0;
	for(int i=1;i<=n;i++){
		if(X[rk[i]]!=X[rk[i-1]]||Y[rk[i]]!=Y[rk[i-1]]||Z[rk[i]]!=Z[rk[i-1]])
			uni++,Q[uni]=(node){Y[rk[i]],Z[rk[i]],uni};
		cnt[uni]++;
	}
	CDQ(1,uni);
	for(int i=1;i<=uni;i++)ans[res[i]+cnt[i]-1]+=cnt[i];
	for(int i=0;i<n;i++)printf("%d\n",ans[i]);
	return (0-0);
}

四维偏序

HDOJ 5126

  1. 向集合中加入三元组(a,b,c)

  2. 查询集合中有多少个三元组(a,b,c),满足a1<=a<=a2, b1<=b<=b2, c1<=c<=c2

对查询操作进行差分,分成8个只查询前缀的操作。

  1. 首先对第一维(即时间维)进行CDQ分治。对于分治区间 [ L , R ] [L,R] [L,R] ,用左半区间的插入操作更新右半区间的查询操作。

  2. 在这个区间内,再对第二维(即 a a a 维)进行排序,排序的第二关键字为第一维(即时间维)。

    再对这个区间内的第二维(即 a a a 维)进行CDQ分治,用排序后 a a a 维前一半小的操作去跟新 a a a 维后一半的查询。

  3. 在第二层的CDQ中,再对第三维(即 b b b 维)进行排序,排序的第二关键字为第一维(即时间维),从小到大扫描每一个操作:

  4. 对于修改操作,只有当这个操作满足第一维在第一层CDQ的左半区间且第二维在第二层CDQ的左半区间,
    才将当前的第四维(即 c c c 维)加入树状数组中。(第四维在预处理时需要离散)

  5. 对于查询操作,只有当这个操作满足第一维在第一层CDQ的右半区间且第二维在第二层CDQ的右半区间,
    才将当前的第四维在树状数组中查询,更新答案。

const int M=5e4+5;
const int LEFT=1;
const int RIGHT=2;
int cas,n,q,uni,OP[M],ans[M],A[M<<1],bit[M<<1];
void add(int x,int v){
	while(x<=uni)bit[x]+=v,x+=x&-x;
}
int query(int x){
	int res=0;
	while(x)res+=bit[x],x-=x&-x;
	return res;
}
struct node{
	int id,x,y,z,op,part1,part2;
}Q[M<<3],tmp[M<<3];
bool cmp1(node &A,node &B){
	if(A.x!=B.x)return A.x<B.x;
	return A.id<B.id;
}
bool cmp2(node &A,node &B){
	if(A.y!=B.y)return A.y<B.y;
	return A.id<B.id;
}
void cdq(int l,int r){
	if(l==r)return;
	int mid=l+r>>1;
	cdq(l,mid),cdq(mid+1,r);
	for(int i=l;i<=mid;i++)Q[i].part2=LEFT;
	for(int i=mid+1;i<=r;i++)Q[i].part2=RIGHT;
	int a=l,b=mid+1,now=l;//使用归并排序提高效率
	while(a<=mid&&b<=r){
		if(cmp2(Q[a],Q[b]))tmp[now++]=Q[a++];
		else tmp[now++]=Q[b++];
	}
	while(a<=mid)tmp[now++]=Q[a++];
	while(b<=r)tmp[now++]=Q[b++];
	for(int i=l;i<=r;i++)Q[i]=tmp[i];
	//sort(Q+l,Q+r+1,cmp2);//用归并排序代替
	for(int i=l;i<=r;i++){
		if(Q[i].op==0&&Q[i].part1==LEFT&&Q[i].part2==LEFT)add(Q[i].z,1);
		if(Q[i].op!=0&&Q[i].part1==RIGHT&&Q[i].part2==RIGHT)ans[Q[i].id]+=Q[i].op*query(Q[i].z);
	}
	for(int i=l;i<=r;i++)if(Q[i].op==0&&Q[i].part1==LEFT&&Q[i].part2==LEFT)add(Q[i].z,-1);
}
void CDQ(int l,int r){
	if(l==r)return;
	int mid=l+r>>1;
	CDQ(l,mid),CDQ(mid+1,r);
	for(int i=l;i<=mid;i++)Q[i].part1=LEFT;
	for(int i=mid+1;i<=r;i++)Q[i].part1=RIGHT;
	sort(Q+l,Q+r+1,cmp1);
	cdq(l,r);
}
int main(){
#ifndef ONLINE_JUDGE
	freopen("jiedai.in","r",stdin);
#endif
	rd(cas);
	while(cas--){
		n=uni=0;
		memset(ans,0,sizeof(ans));
		rd(q);
		for(int i=1;i<=q;i++){
			int x,y,z,x2,y2,z2;
			rd(OP[i]),rd(x),rd(y),rd(z);
			if(OP[i]==1){
				Q[++n]=(node){i,x,y,z,0,0,0};
				A[++uni]=z;
			}
			else{
				rd(x2),rd(y2),rd(z2);
				A[++uni]=z2;
				A[++uni]=z-1;
				Q[++n]=(node){i,x2,y2,z2,1,0,0};
				Q[++n]=(node){i,x-1,y2,z2,-1,0,0};
				Q[++n]=(node){i,x2,y-1,z2,-1,0,0};
				Q[++n]=(node){i,x2,y2,z-1,-1,0,0};
				Q[++n]=(node){i,x-1,y-1,z2,1,0,0};
				Q[++n]=(node){i,x-1,y2,z-1,1,0,0};
				Q[++n]=(node){i,x2,y-1,z-1,1,0,0};
				Q[++n]=(node){i,x-1,y-1,z-1,-1,0,0};
			}
		}
		sort(A+1,A+1+uni);
		uni=unique(A+1,A+1+uni)-A-1;
		for(int i=1;i<=n;i++)Q[i].z=lower_bound(A+1,A+1+uni,Q[i].z)-A;
		CDQ(1,n);
		for(int i=1;i<=q;i++)if(OP[i]==2)printf("%d\n",ans[i]);
	}
	return (0-0);
}

整体二分*

Splay Tree

代码一:权值建树 P3369

  1. 插入 v
  2. 删除 v (若有多个相同的数,只删除一个)
  3. 查询 v 的排名 (比 v 小的数的个数 +1 )
  4. 查询排名为 k 的数
  5. 求小于 v 的最大的数
  6. 求大于 v 的最小的数
struct Splay_Tree{
	static const int SIZE=1e6+5;
	int trid,root,par[SIZE],ch[SIZE][2],val[SIZE],cnt[SIZE],sz[SIZE];
	void up(int x){
		sz[x]=sz[ch[x][0]]+sz[ch[x][1]]+cnt[x];
	}
	void UP(int x){
		while(x)up(x),x=par[x];
	}
	void clean_node(int x){
		par[x]=ch[x][0]=ch[x][1]=val[x]=cnt[x]=sz[x]=0;
	}
	void new_node(int &x,int f,int v){
		x=++trid;
		par[x]=f;
		val[x]=v;
		cnt[x]=sz[x]=1;
		ch[x][0]=ch[x][1]=0;
		UP(x);//单点插入时加上,建树时不加
	}
	void rotate(int x){
		int y=par[x],s=ch[y][0]==x;
		if(ch[x][s])par[ch[x][s]]=y;
		ch[y][!s]=ch[x][s];
		par[x]=par[y];
		if(par[y])ch[par[y]][ch[par[y]][1]==y]=x;
		par[y]=x;
		ch[x][s]=y;
		up(y),up(x);//order is important
	}
	void splay(int x,int goal=0){
		while(par[x]!=goal){
			int y=par[x];
			if(par[y]!=goal){
				if(ch[par[y]][0]==y^ch[y][0]==x)rotate(x);
				else rotate(y);
			}
			rotate(x);
		}
		if(goal==0)root=x;
	}
	void insert(int v){
		if(root==0){
			new_node(root,0,v);
			return;
		}
		for(int x=root;;x=ch[x][v>val[x]]){
			if(v==val[x]){
				cnt[x]++;
				UP(x);
				splay(x);
				return;
			}
			if(ch[x][v>val[x]]==0){
				new_node(ch[x][v>val[x]],x,v);
				splay(ch[x][v>val[x]]);
				return;
			}
		}
	}
	int get_pre(){
		int x=ch[root][0];
		if(x==0)return 0;
		while(ch[x][1])x=ch[x][1];
		return x;
	}
	int Find(int v){
		for(int x=root;x;x=ch[x][v>val[x]])
			if(v==val[x])return x;
		return 0;
	}
	void erase(int v){
		int x=Find(v);
		if(x==0)return;
		splay(x);
		cnt[x]--,sz[x]--;
		if(cnt[x]>0)return;
		int y=get_pre();
		if(y==0)par[root=ch[x][1]]=0;
		else{
			splay(y,x);
			par[ch[x][1]]=y;
			ch[y][1]=ch[x][1];
			up(y);
			par[root=y]=0;
		}
		clean_node(x);
	}
	int query_kth(int k){
		int x=root;
		while(1){
			if(k<=sz[ch[x][0]])x=ch[x][0];
			else if(k<=sz[ch[x][0]]+cnt[x]){splay(x);return val[x];}//将查询的点旋转到根,保证复杂度
			else k-=sz[ch[x][0]]+cnt[x],x=ch[x][1];
		}
	}
	int query_rank(int v){
		int res=0,s=root;
		for(int x=root;x;s=x,x=ch[x][v>val[x]]){
			if(v==val[x]){res+=sz[ch[x][0]];break;}
			if(v>val[x])res+=sz[ch[x][0]]+cnt[x];
		}
		splay(s);//将查询的点旋转到根,保证复杂度
		return res+1;
	}
	int query_less(int v){
		int res=-2e9,s=root;
		for(int x=root;x;s=x,x=ch[x][v>val[x]])if(val[x]<v)MAX(res,val[x]);
		splay(s);//将查询的点旋转到根,保证复杂度
		return res;
	}
	int query_greater(int v){
		int res=2e9,s=root;
		for(int x=root;x;s=x,x=ch[x][v>=val[x]])if(val[x]>v)MIN(res,val[x]);
		splay(s);//将查询的点旋转到根,保证复杂度
		return res;
	}
}splay;

代码二:下标建树 P3391

struct Splay_Tree{
	static const int SIZE=1e6+5;
	int trid,root,par[SIZE],ch[SIZE][2],flip[SIZE],val[SIZE],sz[SIZE];
	void up(int x){
		sz[x]=sz[ch[x][0]]+sz[ch[x][1]]+1;
	}
	void UP(int x){
		while(x)up(x),x=par[x];
	}
	void down(int x){
		if(flip[x]){
			swap(ch[x][0],ch[x][1]);
			if(ch[x][0])flip[ch[x][0]]^=1;
			if(ch[x][1])flip[ch[x][1]]^=1;
			flip[x]=0;
		}
	}
	void DOWN(int x){
		static int top,stk[SIZE];
		while(x)stk[++top]=x,x=par[x];
		while(top)down(stk[top--]);
	}
	void clean_node(int x){
		par[x]=ch[x][0]=ch[x][1]=flip[x]=val[x]=sz[x]=0;
	}
	void new_node(int &x,int f,int v){
		x=++trid;
		par[x]=f;
		val[x]=v;
		sz[x]=1;
		ch[x][0]=ch[x][1]=flip[x]=0;
//		UP(x);//单点插入时加上,建树时不加
	}
	void build(int &x,int f,int l,int r){
		int mid=l+r>>1;
		new_node(x,f,mid);
		if(l<mid)build(ch[x][0],x,l,mid-1);
		if(mid<r)build(ch[x][1],x,mid+1,r);
		up(x);
	}
	void rotate(int x){
		int y=par[x],s=ch[y][0]==x;
		if(ch[x][s])par[ch[x][s]]=y;
		ch[y][!s]=ch[x][s];
		par[x]=par[y];
		if(par[y])ch[par[y]][ch[par[y]][1]==y]=x;
		par[y]=x;
		ch[x][s]=y;
		up(y),up(x);//order is important
	}
	void splay(int x,int goal=0){
		while(par[x]!=goal){
			int y=par[x];
			if(par[y]!=goal){
				if(ch[par[y]][0]==y^ch[y][0]==x)rotate(x);
				else rotate(y);
			}
			rotate(x);
		}
		if(goal==0)root=x;
	}
	int Find_kth(int k){
		int x=root;
		while(1){
			down(x);
			if(k<=sz[ch[x][0]])x=ch[x][0];
			else if(k<=sz[ch[x][0]]+1)return x;
			else k-=sz[ch[x][0]]+1,x=ch[x][1];
		}
	}
	void reverse(int l,int r){
		if(l>=r)return;
		int x=Find_kth(l-1),y=Find_kth(r+1);
		splay(x),splay(y,x);
		flip[ch[y][0]]^=1;
	}
	int query_kth(int k){
		int x=root;
		while(1){
			down(x);
			if(k<=sz[ch[x][0]])x=ch[x][0];
			else if(k<=sz[ch[x][0]]+1){splay(x);return val[x];}//将查询的点旋转到根,保证复杂度
			else k-=sz[ch[x][0]]+1,x=ch[x][1];
		}
	}
}splay;
int main(){
#ifndef ONLINE_JUDGE
	freopen("jiedai.in","r",stdin);
#endif
	int n,q,l,r;
	rd(n),rd(q);
	splay.build(splay.root,0,0,n+1);//将0和n+1插入方便提取区间
	while(q--){
		rd(l),rd(r);
		splay.reverse(l+1,r+1);
	}
	for(int i=1;i<=n;i++)printf("%d%c",splay.query_kth(i+1),i==n?'\n':' ');
	return (0-0);
}

Link Cut Tree

代码一:动态树 P3690

struct Link_Cut_Tree{
	static const int SIZE=1e5+5;
	int par[SIZE],ch[SIZE][2],flip[SIZE],val[SIZE],sum[SIZE];
	bool is_root(int x){//splay's root
		return ch[par[x]][0]!=x&&ch[par[x]][1]!=x;
	}
	void up(int x){
		sum[x]=val[x]^sum[ch[x][0]]^sum[ch[x][1]];
	}
	void down(int x){
		if(flip[x]){
			swap(ch[x][0],ch[x][1]);
			if(ch[x][0])flip[ch[x][0]]^=1;
			if(ch[x][1])flip[ch[x][1]]^=1;
			flip[x]=0;
		}
	}
	void DOWN(int x){
		static int top,stk[SIZE];
		for(stk[++top]=x;!is_root(x);x=par[x])stk[++top]=par[x];
		while(top)down(stk[top--]);
	}
	void rotate(int x){
		int y=par[x],s=ch[y][0]==x;
		if(ch[x][s])par[ch[x][s]]=y;
		ch[y][!s]=ch[x][s];
		par[x]=par[y];
		if(!is_root(y))ch[par[y]][ch[par[y]][1]==y]=x;
		par[y]=x;
		ch[x][s]=y;
		up(y),up(x);//order is important
	}
	void splay(int x){
		DOWN(x);
		while(!is_root(x)){
			int y=par[x];
			if(!is_root(y)){
				if(ch[par[y]][0]==y^ch[y][0]==x)rotate(x);
				else rotate(y);
			}
			rotate(x);
		}
		up(x);
	}
	void Access(int x){
		int s=0;
		while(x){
			splay(x);
			ch[x][1]=s;
			up(x);
			x=par[s=x];
		}
	}
	void make_root(int x){
		Access(x);
		splay(x);
		flip[x]^=1;
	}
	void split(int x,int y){
		make_root(x);
		Access(y);
		splay(y);
	}
	int find_root(int x){//tree's root
		Access(x);
		splay(x);
		while(down(x),ch[x][0])x=ch[x][0];
		splay(x);
		return x;
	}
	void link(int x,int y){
		make_root(x);
		if(find_root(y)==x)return;
		par[x]=y;
		splay(x);
	}
	void cut(int x,int y){
		if(find_root(x)!=find_root(y))return;
		split(x,y);
		if(par[x]!=y||ch[x][1])return;
		par[x]=ch[y][0]=0;
		up(y);
	}
	void update(int x,int v){
		splay(x);
		val[x]=v;
		up(x);
	}
	int query(int x,int y){
		split(x,y);
		return sum[y];
	}
}lct;

代码二:动态图连通性*


KD Tree

代码一:单点插入+矩阵求和 P4148

操作1:在(x,y)上加v

操作2:查询(x1,y1)到(x2,y2)的矩阵内的数字和

强制在线

  1. K-D树:

    以2_D树为例,树上的每个节点都“管理”着一个矩形区域,每个节点上存一个点的坐标,这个坐标一定位于这个节点“管理”的区域内,

    接下来按照这个点的某一维(x或y)上的值将矩阵划分为两个子矩阵(对应由两个子节点“管理”)。

    这样就将多维空间不断地通过某一维度的中位数点进行当前空间的分割,于是平衡时树的高度是 O ( l o g 2 n ) O(log_2{n}) O(log2n) 的。

    KD树的查询其实是暴力+剪枝,复杂度可以当作 O ( n ) ) O(\sqrt{n})) O(n ))

  2. 需要维护的基础信息:

    1. t r e e tree tree:存储当前节点的坐标以及权值
    2. l s o n , r s o n lson,rson lson,rson:当前节点的左右儿子
    3. s u m sum sum:当前子树中所有节点的权值和
    4. s i z e size size:当前子树的节点数量
    5. m i [ i ] mi[i] mi[i]:当前子树中第 i i i维的最小值
    6. m x [ i ] mx[i] mx[i]:当前子树中第 i i i维的最大值
  3. 常用基础函数:

    1. nth_element(tmp+l,tmp+mid,tmp+r+1);用O(length)的时间选出中点并将数组分成两边
    2. void rebuild(int &x,int l,int r);对子树进行重构
    3. void to_array(int x);将子树中的所有节点储存入临时数组中
    4. void check(int &x);检查当前子树是否平衡,平衡系数0.75
    5. bool outside(int a1,int a2,int b1,int b2,int x1,int x2,int y1,int y2);判断当前区域(x1,y1)-(x2,y2)是否在查询区域(a1,b1)-(a2,b2)之外
    6. bool inside(int a1,int a2,int b1,int b2,int x1,int x2,int y1,int y2);判断当前区域(x1,y1)-(x2,y2)是否在查询区域(a1,b1)-(a2,b2)之内
const int SIZE=2e5+5;
int TYPE,jiedai;
struct kd_node{
	int dim[2],val;
	bool operator <(const kd_node &A)const{
		return dim[TYPE]<A.dim[TYPE];
	}
}tree[SIZE],tmp[SIZE];
int root,trid,dfsid,rub,bin[SIZE];
int type[SIZE],ls[SIZE],rs[SIZE],mx[SIZE][2],mi[SIZE][2],sz[SIZE];
ll sum[SIZE];
void up(int x){
	for(int i=0;i<2;i++){
		mx[x][i]=mi[x][i]=tree[x].dim[i];
		if(ls[x])MAX(mx[x][i],mx[ls[x]][i]),MIN(mi[x][i],mi[ls[x]][i]);
		if(rs[x])MAX(mx[x][i],mx[rs[x]][i]),MIN(mi[x][i],mi[rs[x]][i]);
	}
	sum[x]=sum[ls[x]]+sum[rs[x]]+tree[x].val;
	sz[x]=sz[ls[x]]+sz[rs[x]]+1;
}
void new_node(int &x,int ty,const kd_node &rhs){
	if(rub)x=bin[rub--];
	else x=++trid;
	type[x]=ty;
	tree[x]=rhs;
	ls[x]=rs[x]=0;
	up(x);
}
void clean_node(int x){
	bin[++rub]=x;
	ls[x]=rs[x]=sz[x]=sum[x]=0;
}
void rebuild(int &x,int l,int r){
	int mid=l+r>>1;
	TYPE=(++jiedai)&1;
	nth_element(tmp+l,tmp+mid,tmp+r+1);
	new_node(x,TYPE,tmp[mid]);
	if(l<mid)rebuild(ls[x],l,mid-1);
	if(mid<r)rebuild(rs[x],mid+1,r);
	up(x);
}
void to_array(int x){
	if(ls[x])to_array(ls[x]);
	if(rs[x])to_array(rs[x]);
	tmp[++dfsid]=tree[x];
	clean_node(x);
}
void insert(int &x,const kd_node &rhs){
	if(x==0){
		new_node(x,(++jiedai)&1,rhs);
		return;
	}
	if(rhs.dim[type[x]]<=tree[x].dim[type[x]])insert(ls[x],rhs);
	else insert(rs[x],rhs);
	up(x);
	if(sz[ls[x]]>sz[x]*0.75||sz[rs[x]]>sz[x]*0.75){
		dfsid=0;
		to_array(x);
		rebuild(x,1,dfsid);
	}
}
ll query(int x,int a1,int a2,int b1,int b2){//a1<=x<=a2 b1<=y<=b2
	if(x==0)return 0;
	if(mi[x][0]>a2||mx[x][0]<a1||mi[x][1]>b2||mx[x][1]<b1)return 0;
	if(mi[x][0]>=a1&&mx[x][0]<=a2&&mi[x][1]>=b1&&mx[x][1]<=b2)return sum[x];
	if(tree[x].dim[0]>=a1&&tree[x].dim[0]<=a2&&tree[x].dim[1]>=b1&&tree[x].dim[1]<=b2)
		return query(ls[x],a1,a2,b1,b2)+query(rs[x],a1,a2,b1,b2)+tree[x].val;
	return query(ls[x],a1,a2,b1,b2)+query(rs[x],a1,a2,b1,b2);
}
int main(){
#ifndef ONLINE_JUDGE
	freopen("jiedai.in","r",stdin);
#endif
	int n,op,x,y,v,x1,x2,y1,y2,lstans=0;
	rd(n);
	while(1){
		rd(op);
		if(op==1){
			rd(x),rd(y),rd(v);
			x^=lstans,y^=lstans,v^=lstans;
			insert(root,(kd_node){x,y,v});
		}
		else if(op==2){
			rd(x1),rd(y1),rd(x2),rd(y2);
			x1^=lstans,y1^=lstans,x2^=lstans,y2^=lstans;
			printf("%lld\n",lstans=query(root,x1,x2,y1,y2));
		}
		else if(op==3)break;
	}
	return (0-0);
}

代码二:k远点对 P4357

题意:
给定二维平面上的 N N N个点,求第 K K K远的无序点对。

思路:

  1. 别问我为什么想到用K-D Tree的,因为是看了题解的。
  2. 本题没有插入、删除等高级操作,仅仅建树和查询,代码简洁。
  3. 进入正题:考虑暴力,暴力遍历对于每个点而言能形成的所有点对,显然复杂度为 O ( n 2 ) O(n^2) O(n2),不可行,接下来考虑剪枝。
  4. 首先, K = m i n ( 100 , n ∗ ( n + 1 ) 2 ) K=min(100,\frac{n*(n+1)}{2}) K=min(100,2n(n+1)),所以先在小顶堆中插入 2 ∗ K 2*K 2K 0 0 0,在后续暴力搜索前 2 ∗ K 2*K 2K大点对的过程中逐渐把它们 p o p pop pop掉。
  5. 对这 N N N个点的每个点而言,都从K-D Tree的根节点往下遍历,每到一个节点,先计算当前节点与这个点的距离,并更新小顶堆,然后进入到剪枝的关键步骤。
  6. 我们考虑每个节点的左右儿子,分别利用左右儿子的每个维度最大最小边界来计算可能的最远点,若当前子空间最远的点都无法对小顶堆进行更新,则不需要进入这个空间了!
  7. 另一点剪枝:先遍历左右子空间中最远可能点更远的子空间,这样也许就不用再遍历另外一个空间啦。
  8. 复杂度的话。。。还不会算,据说是 O ( n 3 2 ) O(n^{\frac{3}{2}}) O(n23)
const int SIZE=1e5+5;
int TYPE,jiedai;
struct kd_node{
	int dim[2];
	bool operator <(const kd_node &A)const{
		return dim[TYPE]<A.dim[TYPE];
	}
	ll calc(kd_node &A){
		return 1ll*(dim[0]-A.dim[0])*(dim[0]-A.dim[0])+1ll*(dim[1]-A.dim[1])*(dim[1]-A.dim[1]);
	}
	ll calc(int mx[2],int mi[2]){
		int d1=max(abs(dim[0]-mx[0]),abs(dim[0]-mi[0]));
		int d2=max(abs(dim[1]-mx[1]),abs(dim[1]-mi[1]));
		return 1ll*d1*d1+1ll*d2*d2;
	}
}tree[SIZE],tmp[SIZE];
int root,trid;
int type[SIZE],ls[SIZE],rs[SIZE],mx[SIZE][2],mi[SIZE][2];
void up(int x){
	for(int i=0;i<2;i++){
		mx[x][i]=mi[x][i]=tree[x].dim[i];
		if(ls[x])MAX(mx[x][i],mx[ls[x]][i]),MIN(mi[x][i],mi[ls[x]][i]);
		if(rs[x])MAX(mx[x][i],mx[rs[x]][i]),MIN(mi[x][i],mi[rs[x]][i]);
	}
}
void new_node(int &x,int ty,const kd_node &rhs){
	x=++trid;
	type[x]=ty;
	tree[x]=rhs;
	ls[x]=rs[x]=0;
	up(x);
}
void build(int &x,int l,int r){
	int mid=l+r>>1;
	TYPE=(++jiedai)&1;
	nth_element(tmp+l,tmp+mid,tmp+r+1);
	new_node(x,TYPE,tmp[mid]);
	if(l<mid)build(ls[x],l,mid-1);
	if(mid<r)build(rs[x],mid+1,r);
	up(x);
}
priority_queue<ll,vector<ll>,greater<ll>>Q;
void query(int x,kd_node &rhs){
	ll t=rhs.calc(tree[x]),t1=0,t2=0;
	if(t>Q.top())Q.pop(),Q.push(t);
	if(ls[x])t1=rhs.calc(mx[ls[x]],mi[ls[x]]);
	if(rs[x])t2=rhs.calc(mx[rs[x]],mi[rs[x]]);
	if(t1>t2){
		if(t1>Q.top())query(ls[x],rhs);
		if(t2>Q.top())query(rs[x],rhs);
	}
	else{
		if(t2>Q.top())query(rs[x],rhs);
		if(t1>Q.top())query(ls[x],rhs);
	}
}
int n,k;
int main(){
#ifndef ONLINE_JUDGE
	freopen("jiedai.in","r",stdin);
#endif
	rd(n),rd(k);
	for(int i=1;i<=n;i++)rd(tmp[i].dim[0]),rd(tmp[i].dim[1]);
	build(root,1,n);
	for(int i=1;i<=k+k;i++)Q.push(0);
	for(int i=1;i<=n;i++)query(root,tree[i]);
	printf("%lld\n",Q.top());
	return (0-0);
}

左偏树*


双栈模拟队列

loj6515

const int M=5e4+5;
struct Stack{
	int st,top;
	pair<int,int>stk[M];
	void insert(pair<int,int>x){
		stk[++top]=x;
	}
	void pop(){
		top--;
	}
	bool empty(){
		return st==top;
	}
}stk1,stk2;
int casid,m,p;
ll dp1[M][505],dp2[M][505],Q[M],A[M],B[M];
void DP(Stack &stk,ll dp[M][505],int i){
	memset(dp[i],-1,sizeof(dp[i]));
	for(int j=0;j<p;j++)if(~dp[i-1][j]){
		MAX(dp[i][j],dp[i-1][j]);
		MAX(dp[i][(j+stk.stk[i].first)%p],dp[i-1][j]+stk.stk[i].second);
	}
}
void all_DP(Stack &stk,ll dp[M][505]){
	memset(dp[stk.st],-1,sizeof(dp[stk.st]));
	dp[stk.st][0]=0;
	for(int i=stk.st+1;i<=stk.top;i++)DP(stk,dp,i);
}
void rebuild(Stack &stk1,Stack &stk2){
	int mid=(stk1.st+stk1.top+1)/2;
	for(int i=mid;i>stk1.st;i--)stk2.insert(stk1.stk[i]);
	stk1.st=mid;
}
int main(){
#ifndef ONLINE_JUDGE
	freopen("jiedai.in","r",stdin);
#endif
	memset(dp1,-1,sizeof(dp1));
	memset(dp2,-1,sizeof(dp2));
	rd(casid),rd(m),rd(p);
	dp1[0][0]=dp2[0][0]=0;
	for(int k=1;k<=m;k++){
		char str[15];
		int a,b;
		scanf("%s",str);
		if(0);
		else if(str[0]=='I'&&str[1]=='F'){
			rd(a),rd(b);
			stk1.insert(make_pair(a,b));
			DP(stk1,dp1,stk1.top);
		}
		else if(str[0]=='I'&&str[1]=='G'){
			rd(a),rd(b);
			stk2.insert(make_pair(a,b));
			DP(stk2,dp2,stk2.top);
		}
		else if(str[0]=='D'&&str[1]=='F'){
			if(stk1.empty())rebuild(stk2,stk1),all_DP(stk1,dp1),all_DP(stk2,dp2);
			stk1.pop();
		}
		else if(str[0]=='D'&&str[1]=='G'){
			if(stk2.empty())rebuild(stk1,stk2),all_DP(stk1,dp1),all_DP(stk2,dp2);
			stk2.pop();
		}
		else if(str[0]=='Q'&&str[1]=='U'){
			rd(a),rd(b);
			for(int i=0;i<p;i++)A[i]=dp1[stk1.top][i];
			for(int i=0;i<p;i++)B[i]=dp2[stk2.top][i];
			int L=1,R=0;
			ll ans=-1;
			for(int i=a;i<=b;i++){
				while(L<=R&&A[i]>A[Q[R]])R--;
				Q[++R]=i;
			}
			for(int i=p-1;i>=0;i--){
				while(L<=R&&A[(p+b-i)%p]>=A[Q[R]%p])R--;
				Q[++R]=p+b-i;
				while(L<=R&&Q[L]<p+a-i)L++;
				if(L<=R&&~A[Q[L]%p]&&~B[i])MAX(ans,A[Q[L]%p]+B[i]);
			}
			printf("%lld\n",ans);
		}
	}
	return (0-0);
}

哈希表实现map

代码一:STL版本

unordered_map<int,int>mp;

代码二:手写版本(int->int)

struct hash_map{
    static const int SIZE=1e6+5;
	int tot,head[SIZE],key[SIZE*1],value[SIZE*1],nxt[SIZE*1];
	int Hash(int x){
		if(x<0)x=-x;
		return x%(SIZE-1)+1;
	}
	int& operator [](int x){
		int pos=Hash(x);
		for(int i=head[pos];i;i=nxt[i])
			if(key[i]==x)return value[i];
		key[++tot]=x,value[tot]=0,nxt[tot]=head[pos],head[pos]=tot;
		return value[tot];
	}
	void clear(){tot=0;memset(head,0,sizeof(head));}
	hash_map(){clear();}
};

代码三:手写类模板版本(整数->所有类型)

template<class KEY,class VALUE>struct hash_map{
    static const int SIZE=1e6+5;
	int tot,head[SIZE],nxt[SIZE*1];
	KEY key[SIZE*1];
	VALUE value[SIZE*1];
	int Hash(KEY x){
		if(x<0)x=-x;
		return x%(SIZE-1)+1;
	}
	VALUE& operator [](KEY x){
		int pos=Hash(x);
		for(int i=head[pos];i;i=nxt[i])
			if(key[i]==x)return value[i];
		key[++tot]=x,value[tot]=0,nxt[tot]=head[pos],head[pos]=tot;
		return value[tot];
	}
	void clear(){tot=0;memset(head,0,sizeof(head));}
	hash_map(){clear();}
};

支持任意删除的大顶堆

struct Multiset{
	priority_queue<int>A,B;
	void clear(){
		while(!A.empty())A.pop();
		while(!B.empty())B.pop();
	}
	bool empty(){
		return A.empty();
	}
	int top(){
		if(A.empty())return -1e9;
		else return A.top();
	}
    void pop(){
		if(A.empty())return;
		A.pop();
		while(!A.empty()&&!B.empty()&&A.top()==B.top())A.pop(),B.pop();
	}
	void push(int x){
		A.push(x);
	}
	void erase(int x){
		B.push(x);
		while(!A.empty()&&!B.empty()&&A.top()==B.top())A.pop(),B.pop();
	}
};

树论

树的直径

const int SIZE=1e6+5;
int n,tot,head[SIZE],to[SIZE<<1],co[SIZE<<1],nxt[SIZE<<1];
inline void add_edge(int a,int b,int c){
	to[++tot]=b;
	co[tot]=c;
	nxt[tot]=head[a];
	head[a]=tot;
}
int mxID;
ll mxdis;
void dfs(int x,int f,ll d){
	if(d>mxdis)mxdis=d,mxID=x;
	for(int i=head[x];i;i=nxt[i]){
		int y=to[i];
		if(y==f)continue;
		dfs(y,x,d+co[i]);
	}
}
int main(){
#ifndef ONLINE_JUDGE
	freopen("jiedai.in","r",stdin);
#endif
	rd(n);
	for(int i=1;i<n;i++){
		int a,b,c;
		rd(a),rd(b),rd(c);
		add_edge(a,b,c),add_edge(b,a,c);
	}
	int s=0,t=0;
	mxdis=-1,dfs(1,0,0),s=mxID;
	mxdis=-1,dfs(s,0,0),t=mxID;
	printf("%d %d %lld\n",s,t,mxdis);
	return (0-0);
}

树的重心

  1. 删除重心后所得的所有子树,节点数不超过原树的1/2,一棵树最多有两个重心,且它们相邻
  2. 树中所有节点到重心的距离之和最小,如果有两个重心,那么它们距离之和相等
  3. 两个树通过一条边合并,新的重心在原树两个重心的路径上
  4. 树删除或添加一个叶子节点,重心最多只移动一条边
const int SIZE=1e6+5;
int n,tot,head[SIZE],to[SIZE<<1],nxt[SIZE<<1];
inline void add_edge(int a,int b){
	to[++tot]=b;
	nxt[tot]=head[a];
	head[a]=tot;
}
int sz[SIZE],miID,mival;
void dfs(int x,int f){
	sz[x]=1;
	int res=0;
	for(int i=head[x];i;i=nxt[i]){
		int y=to[i];
		if(y==f)continue;
		dfs(y,x);
		sz[x]+=sz[y];
		MAX(res,sz[y]);
	}
	MAX(res,n-sz[x]);
	if(res<mival)mival=res,miID=x;
}
int main(){
#ifndef ONLINE_JUDGE
	freopen("jiedai.in","r",stdin);
#endif
	rd(n);
	for(int i=1;i<n;i++){
		int a,b;
		rd(a),rd(b);
		add_edge(a,b),add_edge(b,a);
	}
	mival=1e9,miID=-1;
	dfs(1,0);
	printf("%d %d\n",miID,mival);
	return (0-0);
}

树上倍增+LCA

const int SIZE=5e5+5;
int tot,head[SIZE],to[SIZE<<1],nxt[SIZE<<1];
int fa[SIZE][20],deep[SIZE];
void add_edge(int a,int b){
	to[++tot]=b;
	nxt[tot]=head[a];
	head[a]=tot;
}
void dfs(int x,int f){
	deep[x]=deep[f]+1;
	fa[x][0]=f;
	for(int i=1;i<20;i++)fa[x][i]=fa[fa[x][i-1]][i-1];
	for(int i=head[x];i;i=nxt[i]){
		int y=to[i];
		if(y==f)continue;
		dfs(y,x);
	}
}
int LCA(int x,int y){
	if(deep[x]<deep[y])swap(x,y);
	int step=deep[x]-deep[y];
	for(int i=0;i<20;i++)if(step&1<<i)x=fa[x][i];
	if(x==y)return x;
	for(int i=19;i>=0;i--)if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];
	return fa[x][0];
}

树链剖分+LCA

const int SIZE=5e5+5;
int tot,head[SIZE],to[SIZE<<1],nxt[SIZE<<1];
int dfsid,fa[SIZE],deep[SIZE],sz[SIZE],son[SIZE],top[SIZE],dfn[SIZE],reid[SIZE];
void add_edge(int a,int b){
	to[++tot]=b;
	nxt[tot]=head[a];
	head[a]=tot;
}
void dfs(int x,int f){
	deep[x]=deep[f]+1;
	fa[x]=f;
	sz[x]=1;
	son[x]=0;
	for(int i=head[x];i;i=nxt[i]){
		int y=to[i];
		if(y==f)continue;
		dfs(y,x);
		sz[x]+=sz[y];
		if(sz[y]>sz[son[x]])son[x]=y;
	}
}
void chain(int x,int k){
	top[x]=k;
	reid[dfn[x]=++dfsid]=x;
	if(son[x])chain(son[x],k);
	for(int i=head[x];i;i=nxt[i]){
		int y=to[i];
		if(y==fa[x]||y==son[x])continue;
		chain(y,y);
	}
}
int LCA(int a,int b){
	while(top[a]!=top[b]){
		if(deep[top[a]]<deep[top[b]])swap(a,b);
		a=fa[top[a]];
	}
	return deep[a]<deep[b]?a:b;
}

RMQ+LCA

O(nlogn) 预处理 O(1) 求LCA

const int SIZE=5e5+5;
int n,tot,head[SIZE],to[SIZE<<1],nxt[SIZE<<1];
int dfsid,deep[SIZE],dfn[SIZE],st[SIZE<<1][20];
void add_edge(int a,int b){
	to[++tot]=b;
	nxt[tot]=head[a];
	head[a]=tot;
}
void dfs(int x,int f){
	deep[x]=deep[f]+1;
	dfn[x]=++dfsid;
	st[dfsid][0]=x;
	for(int i=head[x];i;i=nxt[i]){
		int y=to[i];
		if(y==f)continue;
		dfs(y,x);
		st[++dfsid][0]=x;
	}
}
void RMQ(){
	for(int i=dfsid;i>=1;i--)for(int j=1;i+(1<<j)-1<=dfsid;j++)
		st[i][j]=deep[st[i][j-1]]<deep[st[i+(1<<j-1)][j-1]]?st[i][j-1]:st[i+(1<<j-1)][j-1];
}
int LCA(int x,int y){
	int l=dfn[x],r=dfn[y];
	if(l>r)swap(l,r);
	int k=log2(r-l+1);
	return deep[st[l][k]]<deep[st[r-(1<<k)+1][k]]?st[l][k]:st[r-(1<<k)+1][k];
}

点分治

P3806

n个节点的边权树,m次询问,每次询问树上距离为k的点对是否存在

1 ≤ n ≤ 1 0 4 , 1 ≤ m ≤ 100 , 1 ≤ k ≤ 1 0 7 , 边 权 1 ≤ w ≤ 1 0 4 1≤n≤10^4,1≤m≤100,1≤k≤10^7,边权1≤w≤10^4 1n104,1m100,1k107,1w104

const int M=10005;
const int K=1e7+5;
int tot,head[M],to[M<<1],co[M<<1],nxt[M<<1];
inline void add_edge(int a,int b,int c){
	to[++tot]=b;
	co[tot]=c;
	nxt[tot]=head[a];
	head[a]=tot;
}
int n,m,Q[M],ans[M],cnt[K];
int all,used[M],tmp[M],sz[M],mx[M];
void center(int x,int f){
	sz[x]=1;
	mx[x]=0;
	tmp[++all]=x;
	for(int i=head[x];i;i=nxt[i]){
		int y=to[i];
		if(y==f||used[x])continue;
		center(y,x);
		sz[x]+=sz[y];
		MAX(mx[x],sz[y]);
	}
}
void update(int x,int f,int d,int v){
	if(d<K)cnt[d]+=v;
	for(int i=head[x];i;i=nxt[i]){
		int y=to[i];
		if(y==f||used[y])continue;
		update(y,x,d+co[i],v);
	}
}
void query(int x,int f,int d){
	for(int i=1;i<=m;i++)if(Q[i]-d>=0&&cnt[Q[i]-d])ans[i]=1;
	for(int i=head[x];i;i=nxt[i]){
		int y=to[i];
		if(y==f||used[y])continue;
		query(y,x,d+co[i]);
	}
}
void divide(int x){
	all=0;
	center(x,0);
	for(int i=1;i<=all;i++){
		MAX(mx[tmp[i]],all-sz[tmp[i]]);
		if(mx[tmp[i]]<mx[x])x=tmp[i];
	}
	cnt[0]=1;
	for(int i=head[x];i;i=nxt[i]){
		int y=to[i];
		if(used[y])continue;
		query(y,x,co[i]);
		update(y,x,co[i],1);
	}
	update(x,