首页 > 其他分享 >模板

模板

时间:2023-07-16 18:11:46浏览次数:38  
标签:return lc val int void rc 模板

模板合集

目录

A - 基本算法。

快速幂

ll qpow(ll a,int b){
	ll res=1ll;
	while(b){
		if(b&1) res=res*a%mod;
		a=a*a%mod;b>>=1;
	}
	return res;
}

B - 数据结构。

树状数组

单点加,区间查

//BIT
ll c[N];
int tot;
void ins(int x,ll v){//x!=0
	for(;x<=tot;x+=x&(-x)) c[x]+=v; 
}
ll qry(int x){//x 可以 =0
	ll res=0;
	for(;x;x-=x&(-x)) res+=c[x];
	return res;
}

求全局第 k 小

int kth(int k){
	int p=1<<_log;//_log=ceil(log n)
	for(int i=_log-1;~i;i--){
		int lc=p-(1<<i);
		if(k<=c[lc]) p=lc;//走左子
		else k-=c[lc];//左端点右移到 lc+1
	}
}

线段树

标记永久化

修改:

  • 在途径的每个区间 sum 加上 w 会产生的贡献:w 与交集大小的乘积。
  • 碰到被修改区间完全覆盖的区间时打上 add 标记,说明 w 作用于该区间每个位置。

查询:

  • 被完全覆盖区间只贡献 sum。

  • 沿路的非完全覆盖区间 add 都贡献给 当前区间与查询区间的交集。

用途:

  • 动态开点线段树如果不想下放懒标记时把空儿子建出来。
//permanent tag
const int pd=N<<2;//4N
ll sum[pd],add[pd];
void cg(int s,int e,ll w,int k=1,int l=1,int r=n){
	sum[k]+=(min(r,e)-max(l,s)+1)*w;
	if(s<=l&&r<=e) return add[k]+=w,void();
	if(s<=mid) cg(s,e,w,lc,l,mid);
	if(e>mid) cg(s,e,w,rc,mid+1,r);
}
ll qry(int s,int e,int k=1,int l=1,int r=n){
	if(s<=l&&r<=e) return sum[k];
	ll cur=(min(r,e)-max(l,s)+1)*add[k];
	if(s<=mid) cur+=qry(s,e,lc,l,mid);
	if(e>mid) cur+=qry(s,e,rc,mid+1,r);
	return cur;
}

动态开点线段树

空间 2N。

//dynamic building SGT
#define mid ((l+r)>>1)
#define lc t[k].ls
#define rc t[k].rs
const int pd=N<<1;//2N
int nc,rt;
struct node{
	int ls,rs;
	ll sum,tag;
}t[pd];
void upd(int k){
	t[k].sum=t[lc].sum+t[rc].sum;
}
void one(int &k,int l,int r,ll w){//下放时若儿子不存在要新建儿子
	if(!k) k=++nc;
	t[k].sum+=(r-l+1)*w;
	t[k].tag+=w;
}
void dwn(int k,int l,int r){
	if(t[k].tag){
		ll &w=t[k].tag;
		one(lc,l,mid,w);one(rc,mid+1,r,w);
		w=0; 
	}
}
void cg(int s,int e,ll w,int &k,int l=1,int r=n){
	if(!k) k=++nc;
	if(s<=l&&r<=e) return one(k,l,r,w);
	dwn(k,l,r);
	if(s<=mid) cg(s,e,w,lc,l,mid);
	if(e>mid) cg(s,e,w,rc,mid+1,r);
	upd(k);
}
ll qry(int s,int e,int k,int l=1,int r=n){
	if(!k) return 0;
	if(s<=l&&r<=e) return t[k].sum;
	dwn(k,l,r);
	ll res=0;
	if(s<=mid) res=qry(s,e,lc,l,mid);
	if(e>mid) res+=qry(s,e,rc,mid+1,r);
	return res;
}

线段树合并&分裂

空间 2nlogN。n 为插入元素个数,N 为线段树范围。40倍左右。

分裂:

  • 将 SGT 分为两个副本,x 中只保留 <=key 的树枝,y 中 >key。
  • 副本树的左右子没有变,只是比原树少了一些枝。
  • 原树 p 往左子走,x,y 就都往左子走。同方向。
  • 除非 mid==val,否则若 mid 归 x ,就有 x=p
//merge,split SGT
#define mid ((l+r)>>1)
#define lc(p) t[p].ls
#define rc(p) t[p].rs
const int pd=N*40;//
int nc;
struct node{
	int ls,rs,ct;
}t[pd];
void upd(int k){
	t[k].ct=t[lc(k)].ct+t[rc(k)].ct;
}
int merge(int p,int q){
	if(!p||!q) return p^q;
	t[p].ct+=t[q].ct;
	lc(p)=merge(lc(p),lc(q));
	rc(p)=merge(rc(p),rc(q));
	upd(p);
}
/*可持久化
int merge(int p,int q){
	if(!p||!q) return p^q;
	int u=++nc;
	t[u].ct=t[p].ct+t[q].ct;
	lc(u)=merge(lc(p),lc(q));
	rc(u)=merge(rc(p),rc(q));
	upd(u);
} 
*/
void spv(int val,int p,int l,int r,int &x,int &y){//split by val
	if(!p) return x=y=0,void();//动态开点可能有空点
	if(mid<val) x=p,y=++nc,spv(val,rc(p),mid+1,r,rc(x),rc(y));
	else if(mid==val) x=++nc,y=++nc,lc(x)=lc(p),rc(y)=rc(q); 
	else y=p,x=++nc,spv(val,lc(p),l,mid,lc(x),lc(y));
	upd(x),upd(y);
}
void spz(int sz,int p,int l,int r,int &x,int &y){//split by cnt
	if(!p) return x=y=0,void();
	int cnt=t[lc(p)].ct;
	if(cnt<sz) x=p,y=++nc,spz(sz,rc(p),mid+1,r,rc(x),rc(y));
	else if(cnt==sz) x=++nc,y=++nc,lc(x)=lc(p),rc(y)=rc(p);
	else y=p,x=++nc,spz(sz,lc(p),l,mid,lc(x),lc(y));
	upd(x),upd(y);
}

主席树( 可持久化权值线段树 )

空间 N<<5。(单点加)

  • 可作为线段树的前缀和使用。
  • 可解决区间第 k 小。
  • 和动态开点不同,每次都要新建点 p,不管 p 是否有值。
//主席树 
#define mid ((l+r)>>1)
#define lc(p) t[p].ls
#define rc(p) t[p].rs
const int pd=N<<5;
int nc;
struct node{
	int ls,rs,ct;
}t[pd];
void ins(int &p,int q,int x,int w,int l=1,int r=n){
	p=++nc;//每次都要新建点
	t[p]=t[q];t[p].ct+=w;
	if(x<=mid) ins(lc(p),lc(q),x,w,l,mid);
	else ins(rc(p),rc(q),x,w,mid+1,r); 
}
//主席树区间修改,标记永久化 
#define mid ((l+r)>>1)
#define lc(p) t[p].ls
#define rc(p) t[p].rs
const int pd=(N<<6)+10;
int nc;
struct node{
	int ls,rs;
	ll sum,add;
}t[pd];
void cg(int &p,int q,int s,int e,ll w,int l=1,int r=n){
	p=++nc;t[p]=t[q];
	t[p].sum+=(min(r,e)-max(l,s)+1)*w;
	if(s<=l&&r<=e) return t[p].add+=w,void();
	if(s<=mid) cg(lc(p),lc(q),s,e,w,l,mid);
	if(e>mid) cg(rc(p),rc(q),s,e,w,mid+1,r);
}
ll qry(int p,int s,int e,int l=1,int r=n){
	if(!p) return 0;
	if(s<=l&&r<=e) return t[k].sum;
	ll cur=(min(r,e)-max(l,s)+1)*t[k].add;
	if(s<=mid) cur+=qry(lc(p),s,e,l,mid);
	if(e>mid) cur+=qry(rc(p),s,e,mid+1,r);
	return cur;
} 

李超线段树

//李超线段树
const int pd=N<<1;//动态开点,若无 merge 
#define mid ((l+r)>>1)
#define lc(k) t[k].ls
#define rc(k) t[k].rs
struct func{
	ll k,b;
	func(){}
	func(ll K,ll B):k(K),b(B){}
	ll calc(ll x){
		return k*x+b;
	}
}; 
struct node{
	int ls,rs;
	func f;
}t[pd];
int nc,rt[N];
void ins(int &p,int l,int r,func g){
	if(!p){
		p=++nc;t[p].f=g;
		return;
	}
	ll fl=t[p].f.calc(l),fr=t[p].f.calc(r),
	   gl=g.calc(l),gr=g.calc(r);
	if(fl>=gl&&fr>=gr) return;
	else if(gl>=fl&&gr>=fr) return t[p].f=g,void();
	
	if(t[p].f.calc(mid)<g.calc(mid))
		swap(t[p].f,g),swap(fl,gl);
	if(gl>=fl) ins(lc(p),l,mid,g);
	else ins(rc(p),mid+1,r,g); 
}
void cg(int &p,int s,int e,func g,int l=1,int r=n){
	if(!p) p=++nc;
	if(s<=l&&r<=e) return ins(p,l,r,g);
	if(s<=mid) cg(lc(p),s,e,g,l,mid);
	if(e>mid) cg(rc(p),s,e,g,mid+1,r);
}
int merge(int p,int q,int l=1,int r=n){
	if(!p||!q) return p^q;
	lc(p)=merge(lc(p),lc(q),l,mid);
	rc(p)=merge(rc(p),rc(q),mid+1,r);
	ins(p,l,r,t[q].f);
	return p;
}
ll qry(int x,int p,int l=1,int r=n){
	if(!p) return -INF;
	if(l==r) return t[p].f.calc(x);
	if(x<=mid) return max(t[p].f.calc(x),qry(x,lc(p),l,mid));//标记永久化 
	else return max(t[p].f.calc(x),qry(x,rc(p),mid+1,r));//沿路 f 都作用于 x 
}

楼房重建式线段树

https://www.cnblogs.com/PinkRabbit/p/Segment-Tree-and-Prefix-Maximums.html

维护全局所有前缀最大值的信息。

  • mx[k]:区间最大值。
  • ct[k]:只考虑该区间内所有数时,右子区间的答案(信息“和”)。叶子处无定义。
  • calc(k,pre):考虑在该区间前有个最大值 pre 的影响时,该区间内的答案。
1689300880858

平衡树

splay

//splay
#define lc(p) t[p].son[0]
#define rc(p) t[p].son[1]
#define fa(p) t[p].fa
int nc,rt;
struct node{
	int son[2],fa;
	//lc:val<=t[x].val 
	//rc:val>t[x].val
	int val,sz;
	bool rev;
	void mkrev(){
		swap(lc,rc);rev^=1;
	}
}t[N];
int nd(int p,int val){
	int x=++nc;
	fa(x)=p;
	t[p].son[val>t[p].val]=x;
	t[x].val=val;t[x].sz=1;
	return x;
}
void upd(int x){
	t[x].sz=t[lc(x)].sz+1+t[rc(x)].sz;
}
void dwn(int x){
	if(t[x].rev){
		if(lc(x)) t[lc(x)].mkrev();
		if(rc(x)) t[rc(x)].mkrev();
		t[x].rev=0;
	}
}
int dir(int x){
	return rc(fa(x))==x;
}
void rotate(int x){
	int y=fa(x),z=fa(y),k=dir(x);
	int b=t[x].son[k^1];
	fa(x)=z;if(z) t[z].son[dir(y)]=x;
	t[y].son[k]=b;fa(b)=y;
	t[x].son[k^1]=y;fa(y)=x;
	upd(y);upd(x);
}
void splay(int x,int tar){
	int p;
	while(p=t[x].fa,p!=tar){
		if(fa(p)!=tar) rotate(dir(x)==dir(p)?p:x);
		rotate(x);
	}
	if(!tar) rt=x;
}
int find(int val){
	int x=rt;
	while(x){
		dwn(x);
		if(t[x].val==val) return x;
		x=t[x].son[val>t[x].val];
	}
	return 0;
}
int mx(int x){
	while(dwn(x),rc(x)) x=rc(x);
	return x;
}
void ins(int val){
	int x=rt,p=0;
	while(x){
		dwn(x);p=x;
		x=t[x].son[val>t[x].val];
	}
	x=nd(p,val);
	splay(x,0);
}
void del(int val){
	int x=find(val);
	if(!x) return;
	
	splay(x,0);
	if(lc(x)){
		int p=mx(lc(x));splay(p,x);
		t[p].fa=0;rt=p;
		rc(p)=rc(x);fa(rc(x))=p;
	}
	else rt=rc(x),fa(rc(x))=0;
}
int ask_rk(int val){
	int x=rt,p=0,ct=0;
	while(x){
		dwn(x);p=x;
		if(t[x].val<val){
			ct+=t[lc(x)].sz+1;
			x=rc(x);
		}
		else x=lc(x);
	}
	splay(p,0);
	return ct+1;//+1
}
int ask_val(int k){
	int x=rt,p=0,res=0;
	while(x){
		dwn(x);p=x;
		if(t[lc(x)].sz+1==k){
			res=t[x].val;break;
		}
		else if(k<=t[lc(x)].sz) x=lc(x);
		else k-=t[lc(x)].sz+1,x=rc(x);
	}
	splay(p,0);
	return res;
}
int prev(int val){
	int x=rt,p=0,res=0;
	while(x){
		dwn(x);p=x;
		if(t[x].val>=val) x=lc(x);
		else{
			res=max(res,t[x].val);
			x=rc(x);
		}
	}
	splay(p,0);
	return res;
}
int sufv(int val){
	int x=rt,p=0,res=INF;
	while(x){
		dwn(x);p=x;
		if(t[x].val<=val) x=rc(x);
		else{
			res=min(res,t[x].val);
			x=lc(x);
		}
	}
	splay(p,0);
	return res;
}

fhq_treap

//fhq_treap
namespace BST {
	int nClock, root;
	struct node {
		int lc, rc;
		int val, key;
		int sze;
		bool rev;

		void mk_rev() {
			std::swap(lc, rc);
			rev ^= 1;
		}
	} t[N];

	int create(int val) {
		int p = ++ nClock;
		t[p].lc = t[p].rc = 0;
		t[p].val = val, t[p].key = rand();
		t[p].sze = 1;
		t[p].rev = 0;
		return p;
	}

/* 可持久化平衡树
	int copy(int q) {
		int p = ++ nClock; t[p] = t[q];
		return p;
	} 
*/

	void upd(int p) {
		t[p].sze = t[t[p].lc].sze + t[t[p].rc].sze + 1;
	}

	void spread(int p) {
		if (t[p].rev) {
			if (t[p].lc) t[t[p].lc].mk_rev();
			if (t[p].rc) t[t[p].rc].mk_rev();
			t[p].rev ^= 1;
		}
	}

/* 可持久化平衡树
	void spread(int p) {
		if (t[p].rev) {
			if (t[p].lc) t[p].lc = copy(t[p].lc), t[t[p].lc].mk_rev();
			if (t[p].rc) t[p].rc = copy(t[p].rc), t[t[p].rc].mk_rev();
			t[p].rev = 0;
		}
	}
*/

	void split_v(int p, int val, int &x, int &y) {//拆成两棵并集为p,交集为空的树
		if (!p)
			x = y = 0;
		else {
			spread(p);
			if (t[p].val <= val)
				x = p, split_v(t[p].rc, val, t[x].rc, y);
			else
				y = p, split_v(t[p].lc, val, x, t[y].lc);
			upd(p);//p
		}
	}

	void split_s(int p, int sze, int &x, int &y) {
		if (!p)
			x = y = 0;
		else {
			spread(p);
			if (t[t[p].lc].sze < sze)
				x = p, split_s(t[p].rc, sze - t[t[p].lc].sze - 1, t[x].rc, y);
			else
				y = p, split_s(t[p].lc, sze, x, t[y].lc);
			upd(p);
		}
	}

/* 可持久化平衡树
	void split_v(int p, int val, int &x, int &y) {
		if (!p)
			x = y = 0;
		else {
			p = copy(p), spread(p);
			if (t[p].val <= val)
				x = p, split_v(t[p].rc, val, t[x].rc, y);
			else
				y = p, split_v(t[p].lc, val, x, t[y].lc);
			upd(p);
		}
	}

	void split_s(int p, int sze, int &x, int &y) {
		if (!p)
			x = y = 0;
		else {
			p = copy(p), spread(p);
			if (t[t[p].lc].sze < sze)
				x = p, split_s(t[p].rc, sze - t[t[p].lc].sze - 1, t[x].rc, y);
			else
				y = p, split_s(t[p].lc, sze, x, t[y].lc);
			upd(p);
		}
	}
*/

	int merge(int p, int q) {//val[p]<val[q]
		if (!p || !q) return p ^ q;
		if (t[p].key > t[q].key) {
            spread(p);
			t[p].rc = merge(t[p].rc, q), upd(p);
			return p;
		} else {
            spread(q);
			t[q].lc = merge(p, t[q].lc), upd(q);
			return q;
		}
	}

/* 可持久化平衡树 
	int merge(int p, int q) {
		if (!p || !q) return p ^ q;
		if (rand() % (t[p].sze + t[q].sze) < t[p].sze) {
            spread(p);
			t[p].rc = merge(t[p].rc, q), upd(p);
			return p;
		} else {
            spread(q);
			t[q].lc = merge(p, t[q].lc), upd(q);
			return q;
		}
	}
*/
}

笛卡尔树

//笛卡尔树
//key:BST
//val:heap
#define lc(k) t[k].ls
#define rc(k) t[k].rs
struct node{
	int ls,rs;
}t[N];
int rt;
void build(){
	static int tp,stk[N];
	//sort by key
	F(i,1,n){
		int k=tp;
		while(k&&a[stk[k]]<a[i]) k--;
		
		if(k) rc(stk[k])=i;//注意是 stk 中第 k 个,stk[k] 而非 k
		if(k<tp) lc(i)=stk[k+1];
		stk[tp=k+1]=i;
	}
	rt=stk[1];
}

分块

B=sqrt(n);
int t=0;
for(int i=B;i<=n;i+=B) ++t,bl[t]=i-B+1,br[t]=i;
if(br[t]<n) t++,bl[t]=br[t-1]+1,br[t]=n;
F(i,1,n) be[i]=(i-1)/B+1;

莫队

普通莫队

int n,m,a[N];
int B,be[N];
struct qry{
	int l,r,id;
	void sc(int i){
		id=i;l=rd();r=rd();
	}
	bool operator <(const qry &o)const{
		return be[l]^be[o.l]?be[l]<be[o.l]:(be[l]&1?r<o.r:r>o.r);
	}
}q[M];
int main(){
	F(i,1,n) be[i]=(i-1)/B+1;
	F(i,1,m) q[i].sc(i);
	sort(q+1,q+1+m);
	
	int l=1,r=0;
	F(i,1,m){
		while(r<q[i].r) ins(++r);
		while(l>q[i].l) ins(--l);
		while(r>q[i].r) del(r--);
		while(l<q[i].l) del(l++);
		ans[q[i].id]=...;
	}
}

带修莫队

  • 普通莫队是不支持修改的。
  • 可以给每个询问附加一个时间戳 t,来表示回答该次询问时已经经过了多少次修改。转化为三维莫队。
  • 把询问操作 前两维按块编号,第三维按 t 排序。
  • 当 n,m 同阶时,分块大小 $O(n^{\frac 2 3})$,时间复杂度 $O(n^{\frac 5 3})$。
  • 当 n,m 不同阶时,分块大小$O(nm^{-\frac 1 3})$,时间复杂度 $O(nm^{\frac 2 3})$。
int n,m,mc,mq,a[N];
int B,be[N];
struct cg{
	int x,v;
}c[M];
struct qry{
	int l,r,t,id;
	bool operator <(const qry &o)const{
		if(be[l]^be[o.l]) return be[l]<be[o.l];
		if(be[r]^be[o.r]) return be[r]<be[o.r];
		return t<o.t;
	}
}q[M];
int ans[M];
int main(){
	F(i,1,n) be[i]=(i-1)/B+1;
	F(i,1,m){
		if(op[0]=='C'){
			mc++;
			cin>>c[mc].x>>c[mc].v;
		}
		else{
			mq++;
			cin>>q[mq].l>>q[mq].r;
			q[mq].id=mq;q[mq].t=mc;
		}
	}
	sort(q+1,q+1+mq);
	
	int l=1,r=0,t=0;
	F(i,1,mq){
		while(r<q[i].r) ins(a[++r]);
		while(l>q[i].l) ins(a[--l]);
		while(r>q[i].r) del(a[r--]);
		while(l<q[i].l) del(a[l++]);
		while(t<q[i].t){
			t++;
			int x=c[t].x;
			if(q[i].l<=x&&x<=q[i].r) dec(a[x]),add(c[t].v);
			swap(a[x],c[t].v);//不管是否对当前答案有影响,时间到了这个操作就要做进去
            //把 a 原先值存进 c 里,回溯时把 c 的值还原给 a 即可
		}
		while(t>q[i].t){
			int x=c[t].x;
			if(q[i].l<=x&&x<=q[i].r) dec(a[x]),add(c[t].v);//操作和上面一样
			swap(a[x],c[t].v);
			t--;
		}
		ans[q[i].id]=...;
	}
}

回滚莫队

#include<bits/stdc++.h>
#define ll long long 
#define F(i,l,r) for(int i=l;i<=r;i++)
int rd(){
	#define gc getchar
	int x=0,f=1;char c=gc();
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc();}
	while('0'<=c&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=gc();}
	return x*f;
}
const int N=1e5+10,M=1e6+10,S=35;
int n,m,Q,B,bl[N],fa[N],s[N],xov[N],z[N][S],lim[N],*stk[M],val[M],tp,ans[N];
struct edge{
	int u,v,w;
	void sc(){
		u=rd();v=rd();w=rd();
	}
}e[N];
struct ask{
	int l,r,a,b,id;
	void sc(int i){
		id=i;l=rd();r=rd();a=rd();b=rd();
	}
	bool operator <(const ask &rhs)const{
		return bl[l]^bl[rhs.l]?bl[l]<bl[rhs.l]:r<rhs.r;
	}
}q[N];
void cge(int *it,int v){
	stk[++tp]=it;val[tp]=*it;*it=v;
}
void back(int t){
	while(tp>t) *stk[tp]=val[tp],tp--;
}
void basIns(int x,int v){
	for(int i,j=lim[x];v&&(i=std::__lg(v))>=j;){
		if(!z[x][i]){
			cge(z[x]+i,v);
			if(lim[x]==i)
				while(lim[x]<=30&&z[x][lim[x]]) cge(lim+x,lim[x]+1);
			return;
		}
		v^=z[x][i];
	}
}
void ins(int id){
	int x=e[id].u,y=e[id].v,w=e[id].w;
	for(;x^fa[x];x=fa[x]) w^=xov[x];
	for(;y^fa[y];y=fa[y]) w^=xov[y];
	if(x==y) return basIns(x,w);
	if(s[x]<s[y]) std::swap(x,y);
	cge(fa+y,x);
	cge(s+x,s[x]+s[y]);
	cge(xov+y,w);
	for(int i=30;~i;--i)if(z[y][i]) basIns(x,z[y][i]);
}
int qry(int a,int b){
	int res=0;
	for(;a^fa[a];a=fa[a]) res^=xov[a];
	for(;b^fa[b];b=fa[b]) res^=xov[b];
	if(a!=b) return -1;
	for(int i=30;~i;--i)
		res=std::max(res,res^z[a][i]);
	return res;
}
int main(){
	freopen("xor.in","r",stdin);
	freopen("xor.out","w",stdout);
	n=rd();m=rd();Q=rd();
	B=sqrt(m);
	F(i,1,m) e[i].sc(),bl[i]=(i-1)/B+1;
	F(i,1,n) fa[i]=i,s[i]=1;
	F(i,1,Q) q[i].sc(i);
	std::sort(q+1,q+1+Q);
	
	for(int b=1,i=1;b<=bl[m]&&i<=Q;back(0),b++)
		for(int p=b*B,t;i<=Q&&bl[q[i].l]==b;++i){
			if(bl[q[i].r]==b){
				F(j,q[i].l,q[i].r) ins(j);
				ans[q[i].id]=qry(q[i].a,q[i].b);
				back(0);continue;
			}
			while(p<q[i].r) ins(++p);
			t=tp;
			F(j,q[i].l,b*B) ins(j);
			ans[q[i].id]=qry(q[i].a,q[i].b);
			back(t);
		}
	F(i,1,Q) std::cout<<ans[i]<<"\n";
	return 0;
}

树上莫队

  • 欧拉序:进入该节点时记作 in,从该结点离开时记作 out
  • 对于路径 x->y,令 $in_x<in_y$:
    • 若 lca(x,y)=x,则 $[in_x,in_y]$ 中只出现过一次的点为路径上点。
    • 否则,$[out_x,in_y]$ 中只出现过一次的点和 lca 为路径上点。
int n, m;
int a[N];

int tot, head[N], ver[N * 2], Next[N * 2];
void add_edge(int u, int v) {
	ver[++ tot] = v;    Next[tot] = head[u];    head[u] = tot;
}

int dep[N];
int anc[logN + 1][N];

int In[N], Out[N];
int eul_len, eul[N * 2];

int Bn;
int belong[N * 2];

void dfs(int u, int fu) {
	dep[u] = dep[fu] + 1;

	anc[0][u] = fu;
	for (int i = 1; i <= logN; i ++) anc[i][u] = anc[i - 1][anc[i - 1][u]];

	eul[++ eul_len] = u, In[u] = eul_len;

	for (int i = head[u]; i; i = Next[i]) {
		int v = ver[i];
		if (v == fu) continue;

		dfs(v, u);
	}

	eul[++ eul_len] = u, Out[u] = eul_len;
}

int lca(int x, int y) {
	if (dep[x] > dep[y]) std::swap(x, y);
	for (int i = logN; i >= 0; i --)
		if (dep[x] <= dep[y] - (1 << i)) y = anc[i][y];
	if (x == y) return x;
	for (int i = logN; i >= 0; i --)
		if (anc[i][x] != anc[i][y]) x = anc[i][x], y = anc[i][y];
	return anc[0][x];
}

struct ask {
	int l, r, z, id;
	bool operator < (const qry &rhs) const {
		if (belong[l] ^ belong[rhs.l]) return l < rhs.l;
		return belong[l] & 1 ? r < rhs.r : r > rhs.r;
	}
} q[M];

int ans[M]; 

bool state[N];

void attend(int p) {
	state[p] ^= 1;//路径中出现2次的点会被抵消
    state[p] ? add(a[p]) : dec(a[p]);
}

int main() {
	dfs(1, 0);

	for (int i = 1, x, y, z; i <= m; i ++) {
		std::cin >> x >> y, z = lca(x, y);
		if (In[x] > In[y]) std::swap(x, y);

		if (z == x)
			q[i].l = In[x], q[i].r = In[y], q[i].z = 0, q[i].id = i;
		else
			q[i].l = Out[x], q[i].r = In[y], q[i].z = z, q[i].id = i;
	}

	for (int i = 1; i <= eul_len; i ++) belong[i] = (i - 1) / Bn + 1;

	sort(q + 1, q + 1 + m, cmp);

	int l = 1, r = 0;
	for (int i = 1; i <= m; i ++) {
		while (r < q[i].r) attend(eul[++ r]);
		while (l > q[i].l) attend(eul[-- l]);
		while (r > q[i].r) attend(eul[r --]);
		while (l < q[i].l) attend(eul[l ++]);

		if (q[i].z)
			// Calculate ans[q[i].id] with q[i].z
		else
			// Calculate ans[q[i].id]
	}
}

LCA

倍增

void dfs(int u,int fth){
	anc[0][u]=fth;
	dep[u]=dep[fth]+1;
	F(j,1,S-1)if(anc[j-1][u])
		anc[j][u]=anc[j-1][anc[j-1][u]];
		else break;
	for(int v:ver[u])if(v^fth)
		dfs(v,u);
}
int lca(int x,int y){
	if(dep[x]<dep[y]) swap(x,y);
	for(int j=S-1;j>=0;j--)
		if(dep[anc[j][x]]>=dep[y])
			x=anc[j][x];
	if(x==y) return x;
	for(int j=S-1;j>=0;j--)
		if(anc[j][x]^anc[j][y])
			x=anc[j][x],y=anc[j][y];
	return anc[0][x];
}

重链剖分

int lca(int x,int y){
	while(top[x]^top[y]){
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		x=fa[top[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	return x;
}

欧拉序 & ST 表

  • 欧拉序1:第一次访问到该点记为 $fir_x$,以后每次回溯到 x 都把 x 加入欧拉序列。
  • 假设 $fir_x<fir_y$,$[fir_x,fir_y]$ 中深度最小的点即为 lca。
const int M=N<<1;
int eu[M],z[S][M];
void dfs(int u,int fth){
	dep[u]=dep[fth]+1;
	eu[++len]=u;fir[u]=len;
	for(int v:ver[u])if(v^fth){
		dfs(v,u);
		eu[++len]=u;
	}
}
int bet(int x,int y){
	return dep[x]<dep[y]?x:y;
}
int lca(int x,int y){
	x=fir[x];y=fir[y];
	if(x>y) swap(x,y);
	int k=Log[y-x+1];
	return bet(z[k][x],z[k][y-(1<<k)+1]);
}
int main(){
	dfs(1,0);
	Log[0]=-1;
	F(i,1,len) z[0][i]=eu[i],Log[i]=Log[i>>1]+1;
	F(j,1,S-1) F(i,1,n-(1<<j)+1)
		z[j][i]=bet(z[j-1][i],z[j-1][i+1<<(j-1)]);
}

LCT

namespace LCT {
	struct node {
		int son[2], Fa;
		int sze;
		int vir;
		bool rev;

		#define lc son[0]
		#define rc son[1]

		void init() {
			lc = rc = 0, Fa = 0;
			sze = 1;
			vir = 0;
			rev = 0;
		}

		void mk_rev() {
			std::swap(lc, rc);
			rev ^= 1;
		}
	} t[N];

	void upd(int p) {
		t[p].sze = t[t[p].lc].sze + t[t[p].rc].sze + 1 + t[p].vir;
	}

	void spread(int p) {
		if (t[p].rev) {
			if (t[p].lc) t[t[p].lc].mk_rev();
			if (t[p].rc) t[t[p].rc].mk_rev();
			t[p].rev = 0;
		}
	}

	int dir(int x) {
		return t[t[x].Fa].rc == x;
	}

	bool is_top(int x) {
		return t[t[x].Fa].lc != x && t[t[x].Fa].rc != x;
	}

	void rotate(int x) {
		int y = t[x].Fa, z = t[y].Fa, k = dir(x);
		t[x].Fa = z; if (!is_top(y)) t[z].son[t[z].rc == y] = x;
		t[y].son[k] = t[x].son[k ^ 1], t[t[x].son[k ^ 1]].Fa = y;
		t[x].son[k ^ 1] = y, t[y].Fa = x;
		upd(y), upd(x);
	}

	void splay(int x) {
		static int top, stk[N], p;

		stk[top = 1] = x;
		for (p = x; !is_top(p); p = t[p].Fa) stk[++ top] = t[p].Fa;
		while (top) spread(stk[top --]);

		while (p = t[x].Fa, !is_top(x)) {
			if (!is_top(p)) rotate(dir(x) == dir(p) ? p : x);
			rotate(x);
		}
	}

	void access(int x) {
		for (int y = 0; x; y = x, x = t[x].Fa) {
			splay(x);
			t[x].vir += t[t[x].rc].sze - t[y].sze;
			t[x].rc = y;
			upd(x);
		}
	}

	void make_root(int x) {
		access(x), splay(x), t[x].mk_rev();
	}

	void link(int x, int y) {
		if (find_root(x) == find_root(y)) return;
		make_root(x), make_root(y);
		t[x].Fa = y, t[y].vir += t[x].sze, t[y].sze += t[x].sze;
	}

	void cut(int x, int y) {
		make_root(x), access(y), splay(y);
		if (t[y].lc != x || t[x].rc) return;
		t[y].lc = t[x].Fa = 0, upd(y);
	}
}

点分治

int get(int u,int fth){
	sz[u]=1;mx[u]=0;
	for(int v:ver[u]){
		if(ban[v]||v==fth) continue;
		get(v,u);
		sz[u]+=sz[v];
		if(sz[v]>mx[u]) mx[u]=sz[v];
	}
	mx[u]=max(mx[u],asz-mx[u]);
	if(!art||mx[u]<mx[art]) art=u;
}
int solve(int u){
	ban[u]=1;
	//calc path through u
	for(int v:ver[u])if(!ban[v]){
		asz=sz[v];art=0;
		get(v,u);solve(v);
	}
}
int main(){
	asz=n;art=0;
	get(1,0),solve(art);
}

点分树

  • 点分树中任意两点 u,v 的 LCA 一定在原树中 u,v 的简单路径上。
namespace PDT{
	int rt,fa[N];
	vector<int>ver[N];
	void lk(int u,int v){
		ver[u].pb(v);fa[v]=u;
	}
}
int get(int u,int fth){
	sz[u]=1;mx[u]=0;
	for(int v:ver[u]){
		if(ban[v]||v==fth) continue;
		get(v,u);
		sz[u]+=sz[v];
		if(sz[v]>mx[u]) mx[u]=sz[v];
	}
	mx[u]=max(mx[u],asz-mx[u]);
	if(!art||mx[u]<mx[art]) art=u;
}
int solve(int u){
	ban[u]=1;
	for(int v:ver[u])if(!ban[v]){
		asz=sz[v];art=0;
		get(v,u);PDT::lk(u,art);
		solve(v);
	}
}
void build(){
	asz=n;art=0;
	get(1,0);PDT::rt=art;
	solve(art);
}

虚树

bool cmp(int x,int y){
	return dfn[x]<dfn[y];
}
int rt;
void build(){
	sort(h+1,h+1+m,cmp);//所有关键点
	int len=m;
	F(i,1,m) a[i]=h[i];
	F(i,1,m-1) a[++len]=lca(h[i],h[i+1]);
	
	sort(a+1,a+1+len,cmp);//所有虚树上的点
	len=unique(a+1,a+1+len)-a-1;
	
	rt=a[1];
	F(i,1,len-1) lk(lca(a[i],a[i+1]),a[i+1]);
}

DSU on tree

  • dfs 轻儿子的子树,计算它们的答案,要回退影响。
  • dfs 重儿子的子树,计算它的答案,不回退。
  • 加入所有轻儿子子树和 u 自身的影响,计算 u 的答案。
  • 可能要回退 u 子树内所有点的影响。

每个点被遍历到的次数为到根路径上轻边数+1。$O(n\log n)$。

void dfs(int u, int fu) {
	dfsClock ++;
	dfn[u] = dfsClock, idx[dfsClock] = u;

	sze[u] = 1;

	for (int i = head[u]; i; i = Next[i]) {
		int v = ver[i];
		if (v == fu) continue;

		dfs(v, u);

		sze[u] += sze[v];
		if (sze[v] > sze[son[u]]) son[u] = v;
	}
}

void solve(int u, int fu, bool rem) {
	for (int i = head[u]; i; i = Next[i]) {
		int v = ver[i];
		if (v == fu || v == son[u]) continue;

		solve(v, u, 0);
	}

	if (son[u]) solve(son[u], u, 1);

	for (int i = head[u]; i; i = Next[i]) {
		int v = ver[i];
		if (v == fu || v == son[u]) continue;

		int nl = dfn[v], nr = dfn[v] + sze[v] - 1;
		for (int ni = nl; ni <= nr; ni ++) {
			int x = idx[ni];
			add(x);
		}
	}

	add(u);

	// ans[u] = ...

	if (!rem) {
		int nl = dfn[u], nr = dfn[u] + sze[u] - 1;
		for (int ni = nl; ni <= nr; ni ++) {
			int x = idx[ni];
			dec(x);
		}
	}
}

C - 图论

kruskal 重构树

void build() {
		std::sort(e + 1, e + 1 + m);

		nClock = n;//最初就有 n 个点
		for (int i = 1; i <= n; i ++) fa[i] = i;
		for (int i = 1; i <= m; i ++) {
			int u = e[i].u, v = e[i].v, w = e[i].w;
			int p = get(u), q = get(v);

			if (p == q) continue;

			int x = ++ nClock;
			t[x].val = w, t[x].lc = p, t[x].rc = q;

			fa[p] = fa[q] = fa[x] = x;//fa[x]=x
		}
	}
  • 大根堆,二叉树
  • 两点间 LCA 权值即为原图中 x->y 路径上最大边权的最小值。

SPFA

$O(nm)$。

$vis_u$ 表示 u 是否在队列中。

void SPFA() {
	for (int i = 1; i <= n; i ++) dist[i] = inf;
	for (int i = 1; i <= n; i ++) vis[i] = 0;

	std::queue<int> q;
	q.push(1), dist[1] = 0;

	while (q.size()) {
		int u = q.front(); q.pop(); vis[u] = 0;

		for (int i = head[u]; i; i = Next[i]) {
			int v = ver[i], w = edge[i];
			if (dist[u] + w < dist[v]) {
				dist[v] = dist[u] + w;
				if (!vis[v]) q.push(v), vis[v] = 1;
			}
		}
	}
}

判断负环

  • $cnt_x$ 为起点到 x 最短路经过的边数。
  • $cnt_x\geq n$。

圆方树

const int SZ=N*2;
vector<int>ver[N],out[SZ];
int v_dcc;
void tarjan(int u){
	dfn[u]=low[u]=++dfc;
	stk[++tp]=u;
	for(int v:ver[u]){
		if(!dfn[v]){
			tarjan(v);
			low[u]=min(low[u],low[v]);
			if(low[v]==dfn[u]){
				v_dcc++;int p=n+v_dcc,x;
				do{
					x=stk[tp--];
					out[p].pb(x);out[x].pb(p);
				}while(x!=v);
				out[p].pb(u);out[u].pb(p);
			}
		}
		else low[u]=min(low[u],dfn[v]);
	}
}
int main(){
	F(i,1,n)if(!dfn[i])
		tp=0,tarjan(i);//栈清空
}

二分图

判定

无奇环。染色。若无冲突即可。

bool dfs(int u, int color) {//color:1 or 2
	col[u] = color;

	for (int i = head[u]; i; i = Next[i]) {
		int v = ver[i];
		if (col[u] == col[v])
			return 0;
		else if (!col[v] && !dfs(v, 3 - color))
			return 0;
	}

	return 1;
}
二分图最大匹配

匈牙利算法

$O(nm)$。

bool dfs(int u) {
	for (int i = head[u]; i; i = Next[i]) {
		int v = ver[i];
		if (vis[v]) continue;
		vis[v] = 1;
		if (!match[v] || dfs(match[v])) {//若右部有未匹配的 y
			match[v] = u;//或者 y 匹配的 x' 能另找到一个能与它匹配的 y'
			return 1;//就把 y 配给 x
		}
	}

	return 0;
}

int main() {
	int ans = 0;
	for (int i = 1; i <= nl; i ++) {//遍历每个左部点
		memset(vis, 0, sizeof(vis));
		if (dfs(i)) ans ++;
	}
}

字典序最小匹配

  • 左部点不断挤掉右部点匹配的左部点的过程。
  • 左部点由大到小排,左部点的出边由小到大排。

最大流算法

  • 只能判定可行性:mf=nl 即可。
  • $O(m\sqrt n )$。

二分图最大带权匹配:最大费用最大流。

二分图最大多重匹配

连边:

  • $(src,l_i,kl_i)$
  • $(l_i,r_j,1)$
  • $(r_j,des,kr_j)$

跑最大流。

网络流

最大流:$O(n^2m)$。

int et=1;
void add(int u,int v,int f){
	nxt[++et]=head[u];head[u]=et;
	ver[et]=v;cap[et]=f;
}
void lk(int u,int v,int f){
	add(u,v,f);add(v,u,0);
}
bool BFS(){
	F(i,1,nc) cur[i]=head[i],dis[i]=INF,lev[i]=-1;
	while(!q.empty()) q.pop();//注意清空队列
	dis[src]=lev[src]=0;
	q.push(src);
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int e=head[u],v;e;e=nxt[e]){
			v=ver[e];
			if(cap[e]>0&&lev[v]==-1){//>0
				lev[v]=lev[u]+1;
				q.push(v);
				if(v==des) return 1;
			}
		}
	}
	return lev[des]!=-1;
}
int dinic(int u,int flow){
	if(u==des) return flow;
	int res=0,ad;
	for(int &e=cur[u];e;e=nxt[e]){
		int v=ver[e];
		if(cap[e]>0&&lev[v]==lev[u]+1){
			ad=dinic(v,min(flow-res,cap[e]));
			if(ad){
				res+=ad;cap[e]-=ad;cap[e^1]+=ad;
				if(flow==res) break;
			}
		}
	}
	if(res!=flow) lev[u]=-1;
	return res;
}
void mxf(){
	while(BFS()) mf+=dinic(src,INF);
}

费用流

int et=1;
void add(int u,int v,int f,int c){
	nxt[++et]=head[u];head[u]=et;
	ver[et]=v;cap[et]=f;len[et]=c;
}
void lk(int u,int v,int f,int c){
	add(u,v,f,c);add(v,u,0,-c);
}
bool SPFA(){
	F(i,1,nc) cur[i]=head[i],dis[i]=INF,vis[i]=in[i]=0;
	dis[src]=0;q.push(src);in[src]=1;
	while(!q.empty()){
		int u=q.front();q.pop();in[u]=0;
		for(int e=head[u];e;e=nxt[e]){
			int v=ver[e];
			if(cap[e]>0&&dis[v]>dis[u]+len[e]){
				dis[v]=dis[u]+len[e];
				if(!in[v]) q.push(v),in[v]=1;
			}
		}
	}
	return dis[des]<INF;
}
int dinic(int u,int flow){
	if(u==des) return mc+=dis[des]*flow,flow;
	
	vis[u]=1;
	int res=0,ad,v;
	for(int &e=cur[e];e;e=nxt[e])
		if(cap[e]>0&&!vis[v=ver[e]]&&dis[v]==dis[u]+len[e]){
			ad=dinic(v,min(flow-res,cap[e]));
			if(ad){
				res+=ad;cap[e]-=ad;cap[e^1]+=ad;
				if(res==flow) break;
			}
		}
	if(res==flow) vis[u]=0;//vis=0 还可访问这个点
	return res;
}
void mcmf(){
	while(SPFA()) mf+=dinic(src,INF);
}

标签:return,lc,val,int,void,rc,模板
From: https://www.cnblogs.com/nangle/p/17558277.html

相关文章

  • 模板方法模式
    目录1.概述2.结构3.案例实现4.优缺点5.适用场景6.JDK源码解析1.概述在面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。例如,去银......
  • Python练手小项目——简易版基础SQL模板代码生成器
    1、效果图2、代码源码-ui.py:fromtkinterimport*fromtkinterimportscrolledtext,messageboxfromtkinter.ttkimportComboboximportpymysqldefinit():#创建窗口:实例化一个窗口对象window=Tk()#窗口大小window.geometry("900x550")......
  • 矩阵相关模板
    矩阵快速幂#include<cstdio>#include<cstring>#include<algorithm>#include<set>#include<cmath>usingnamespacestd;constintN=150;constintmod=1e9+7;typedeflonglonglld;inlineintread(){ registerintw=......
  • 模板字节转换
    #include<cstdint>#include<cstring>template<typenameT>inlineTparseData(constuint8_t*byteData,size_toffset){Tresult;std::memcpy(&result,byteData+offset,sizeof(T));returnresult;}intmain(){ui......
  • 线段树模板
    单点修改,区间查询给n个数a1,a2,a3,…,an。支持q个操作:1xd,修改ax=d。2lr,查询(l,r),并且求出最小值出现了多少次。输入格式第一行两个整数n,q(1≤n,q≤2×105)。接下来一行n个整数a1,a2,…,an(1≤ai≤109)。接下来q行,每行一个形如1xd或者2lr的操作,保证1≤x≤n,......
  • idea模板
    FileandCodeTemplateclass==1#if(${PACKAGE_NAME}&&${PACKAGE_NAME}!="")package${PACKAGE_NAME};#end#parse("FileHeader.java")/***<h3>${PROJECT_NAME}</h3>*<p>${description}</p>**@autho......
  • go text模板
    packageinstallimport("bytes""fmt""strings""text/template""github.com/fanux/sealos/pkg/logger""sigs.k8s.io/yaml")varConfigTypestringfuncsetKubeadmAPI(versionstring){maj......
  • 洛谷 P3372 【模板】线段树 1
    题目传送门题目描述如题,已知一个数列,你需要进行下面两种操作:1.将某一个数加上x2.求出某区间每一个数的和输入格式第一行包含两个整数N、M,分别表示该数列数字的个数和操作的总个数。第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值。接下来M行每行包含3......
  • 根据模板自动匹配目标字符串
    好的,让我们模拟一下这段代码的运行,并打印出每一行的结果://声明一个静态的正则表达式模式,用于匹配大括号中的内容privatestaticfinalPatternpattern=Pattern.compile("\\{(.*?)\\}");privatestaticMatchermatcher;//字符串格式化替换方法publicStringformatStr......
  • dede共用同一个文章ID展示多个不同的模板页面
    DEDE共用同一个文章ID展示多个不同的模板页面,比如链接:http://jinmengqiang.cn/info-1.htmlhttp://jinmengqiang.cn/plus/show.php?aid=1以上2个链接可以使用不同的模板,其实内容可以相同也可以不同的进行调用(这个需要后台二次开发进行配合)。首先复制/m/view.php并且改名......