我的ACM模板整理

防止卡常的

#pragma GCC optimize(2)
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#pragma GCC optimize("-fgcse")
#pragma GCC optimize("-fgcse-lm")
#pragma GCC optimize("-fipa-sra")
#pragma GCC optimize("-ftree-pre")
#pragma GCC optimize("-ftree-vrp")
#pragma GCC optimize("-fpeephole2")
#pragma GCC optimize("-ffast-math")
#pragma GCC optimize("-fsched-spec")
#pragma GCC optimize("unroll-loops")
#pragma GCC optimize("-falign-jumps")
#pragma GCC optimize("-falign-loops")
#pragma GCC optimize("-falign-labels")
#pragma GCC optimize("-fdevirtualize")
#pragma GCC optimize("-fcaller-saves")
#pragma GCC optimize("-fcrossjumping")
#pragma GCC optimize("-fthread-jumps")
#pragma GCC optimize("-funroll-loops")
#pragma GCC optimize("-fwhole-program")
#pragma GCC optimize("-freorder-blocks")
#pragma GCC optimize("-fschedule-insns")
#pragma GCC optimize("inline-functions")
#pragma GCC optimize("-ftree-tail-merge")
#pragma GCC optimize("-fschedule-insns2")
#pragma GCC optimize("-fstrict-aliasing")
#pragma GCC optimize("-fstrict-overflow")
#pragma GCC optimize("-falign-functions")
#pragma GCC optimize("-fcse-skip-blocks")
#pragma GCC optimize("-fcse-follow-jumps")
#pragma GCC optimize("-fsched-interblock")
#pragma GCC optimize("-fpartial-inlining")
#pragma GCC optimize("no-stack-protector")
#pragma GCC optimize("-freorder-functions")
#pragma GCC optimize("-findirect-inlining")
#pragma GCC optimize("-fhoist-adjacent-loads")
#pragma GCC optimize("-frerun-cse-after-loop")
#pragma GCC optimize("inline-small-functions")
#pragma GCC optimize("-finline-small-functions")
#pragma GCC optimize("-ftree-switch-conversion")
#pragma GCC optimize("-foptimize-sibling-calls")
#pragma GCC optimize("-fexpensive-optimizations")
#pragma GCC optimize("-funsafe-loop-optimizations")
#pragma GCC optimize("inline-functions-called-once")
#pragma GCC optimize("-fdelete-null-pointer-checks")

CF缺省源

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<cmath>
using namespace std;

template<class T>inline void read(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;}
int cansel_sync=(ios::sync_with_stdio(0),cin.tie(0),0);
#define ll long long
#define ull unsigned long long
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repb(i,a,b) for(int i=(a);i>=b;i--)
#define log(x) (31-__builtin_clz(x))
#define INF 0x3f3f3f3f
ll gcd(ll a,ll b){ while(b^=a^=b^=a%=b); return a; }
//#define INF 0x7fffffff

void solve(){
	
}

int main(){
	int z;
	cin>>z;
	while(z--) solve();
}

快读快写

template<class T>inline void read(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>
void wt(T x){//快写
   if(x < 0) putchar('-'), x = -x;
   if(x >= 10) wt(x / 10);
   putchar('0' + x % 10);
}

快速幂
矩阵快速幂

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std; 

#define ll long long 
#define MAXN 110

int n;
const ll mod = 998;

struct Matrix{
	ll a[MAXN][MAXN];
	
	Matrix(ll x=0){//感觉是特别好的初始化,从hjt那里学(抄)来的 
		for(int i=0;i<n;i++){
			for(int j=0;j<n;j++){
				a[i][j]=x*(i==j);//这句特简洁		
			}
		}
	}
	
	Matrix operator *(const Matrix &b)const{//通过重载运算符实现矩阵乘法 
		Matrix res(0);
		for(int i=0;i<n;i++){
			for(int j=0;j<n;j++){
				for(int k=0;k<n;k++){
					ll &ma = res.a[i][j];
					ma = (ma+a[i][k]*b.a[k][j])%mod;
				}
			}
		}
		return res;
	}
};

Matrix qpow(Matrix d,ll m){//底数和幂次数 
	Matrix res(1);//构造E单位矩阵 
	while(m){
		if(m&1){
			m--;//其实这句是可以不要的 
			res=res*d;
		}
		d=d*d;
		m>>=1;
	}
	return res; 
}

int main(){
	int p;
	Matrix inp;
	cin>>n>>p;
	for(int i=-0;i<n;i++){
		for(int j=0;j<n;j++){
			scanf("%lld",&inp.a[i][j]);
		}
	}
	Matrix res = qpow(inp,p);
	for(int i=0;i<n;i++){
		for(int j=0;j<n;j++){
			cout<<res.a[i][j];
			if(j!=n-1) cout<<' ';
		}
		cout<<endl;
	}
}

并查集
路径压缩+树高优化

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;

#define MAXN 100000 

int par[MAXN];//记录父亲节点 
int rank[MAXN];//记录树高,根节点(祖先节点)那层是不算进去的  

void init(int n){
	for(int i=0;i<n;i++){
		par[i]=i;
		rank[i]=0;
	}
} 

int find(int x){//查询根操作,同时进行路径压缩 
	if(par[x]==x) return x;
	else return par[x]=find(par[x]);//这句很精简,在递归查询根节点的同时路i压缩 
} 
//虽然路径压缩的操作会对树高产生影响,导致rank的数值不准确,但这样还是能有效地提高运行效率的 

void merge(int x,int y){
	x=find(x);
	y=find(y);
	if(x==y) return;///如果已经具有相同的祖先,则不进行合并操作
	if(rank[x]<rank[y]) par[x]=y;//y树比x树高的情况,把x并为y的儿子节点 
	else{//y比x高,或同高的情况下,把y并为x的儿子节点 
		par[y]=x;
		if(rank[x]==rank[y]) rank[x]++;//如果合并的两树同高,则合并后树高+1; 
	} 
} 

int main(){
	int n,m;
	cin>>n>>m;
	init(n);
	int a,b;
	while(m--){
		cin>>a>>b;
		merge(a,b);
	}
	cin>>m;
	while(m--){
		cin>>a>>b;
		if(find(a)==find(b)) cout<<"YES"<<endl;
		else cout<<"NO"<<endl;
	}
} 

动态规划DP
最长连续不下降子序列

#include<iostream>
 
using namespace std;

#define MAXN 10000
int main(){
	int n;
	int a[MAXN];
	cin>>n;
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	int now=1,ans=1;
	for(int i=2;i<=n;i++){
		if(a[i]>a[i-1]) now++;
		else now = 1;
		ans = max(now,ans);
	}
	cout<<ans<<endl;
} 

最长不下降子序列

#include<iostream>
#include<cstring>
using namespace std;

#define MAXN 1000
#define INF 0x3f3f3f3f

int main(){
	int n;
	int a[MAXN];//存数组 
	int dp[MAXN];//dp[i]即长度为i的子序列的结尾数字 
	scanf("%d",&n); 
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
	}
	memset(dp,INF,sizeof(dp));
	for(int i=1;i<=n;i++){
		int *px=lower_bound(dp+1,dp+1+n,a[i]);//找到等于它或者小于它的位置 
		*px=a[i];//插入,如果原位置有数据则覆盖,因为同样长度则留结尾数字较小的那个 
	}
	int now=1;
	while(dp[now]!=INF) now++;
	cout<<now-1<<endl;
}

最长公共子序列

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

#define MAXN 1000

int dp[MAXN][MAXN];

int main() {
	dp[0][0]=dp[0][1]=dp[1][0]=0;
	string a,b;
	cin>>a>>b;
	int la=a.size()-1;
	int lb=b.size()-1;
	for(int i=0; i<la; i++) {
		for(int j=0; j<lb; j++) {
			if(a[i]==b[j]) {
				dp[i+1][j+1]=dp[i][j]+1;
			} else {
				dp[i+1][j+1]=max(dp[i+1][j],dp[i][j+1]);
			}
		}
	}
	cout<<dp[la][lb]<<endl;
}

数位DP
HDOJ2089 不要62

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

int l,r;
const int MAXN = 25;
int dp[MAXN][2];//第二维记录前面一个是否为2
int a[MAXN];
int n,m;

int dfs(int pos,bool stat,int pre,bool limit){
    //limit记录之前是否都在上界,state记录之前的是不是6(记忆化作第二维),pre记录上一个数字
	if(pos==-1) return 1;
	if(!limit&&dp[pos][stat]!=-1) return dp[pos][stat];
    int up = limit?a[pos]:9;//如果前面的都在上界则只能到a[pos]
    int ans = 0;
    for(int i=0;i<=up;i++){
        if(pre==6&&i==2) continue;//62的情况
        if(i==4) continue;
		ans += dfs(pos-1,i==6,i,limit&&i==a[pos]);
    }
	if(!limit) dp[pos][stat] = ans;
    return ans;
}

int solve(int x){
	//先按位转化到数组
    int px = 0;
    while(x){
		a[px++] = x%10;
        x/=10;
    }
    memset(dp,-1,sizeof(dp));//清空记忆化数组
    return dfs(px-1,0,-1,1);
}

int main(){
	while(cin>>l>>r){
        if(l==0&&r==0) break;
    	//cout<<solve(r)<<' '<<solve(l-1)<<endl;
        cout<<solve(r)-solve(l-1)<<endl;
    }
}

树状数组
单点更新,区间查询

const int MAXN = 1e6+5;

inline int lowbit(int x){return x&(-x);}

int n;
ll t[MAXN];

inline void init(){
	memset(t,0,sizeof(t));
} 

void add(int x,int k){
	for(;x<=n;x+=lowbit(x)) t[x]+=k;
}

ll sum(int x){
	ll ans = 0;
	for(;x>0;x-=lowbit(x)) ans+=t[x];
	return ans; 
} 

线段树
最普通的线段树,区间修改,区间查询

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;

#define MAXN 10000

struct Tree{
	int sum;
	int lazy;//懒惰标记 ,使得复杂度从nlogn变为logn 
}t[MAXN<<2];

int ans,n,m; 

void pushup(int pos){
	t[pos].sum=t[pos<<1].sum + t[pos<<1|1].sum;//更新父亲节点的数值 
	return;
}

void pushdown(int l,int r,int pos){
	if(!t[pos].lazy) return;//如果没有lazy则不需要pushdown
	int mid = (l+r)>>1;
	t[pos<<1].sum+=t[pos].lazy*(mid-l+1);
	t[pos<<1|1].sum+=t[pos].lazy*(r-(mid+1)+1);
	t[pos<<1].lazy+=t[pos].lazy;//向下传递这个lazy标记 
	t[pos<<1|1].lazy+=t[pos].lazy;
	t[pos].lazy=0; 
} 

void build(int l,int r,int pos){
	t[pos].sum=t[pos].lazy=0;
	if(l==r){//当l==r时说明找到了叶子节点
		scanf("%d",&t[pos].sum);
		return;
	}
	int mid = (l+r)>>1;
	build(l,mid ,pos<<1);//左儿子,树的特性
	build(mid+1,r,pos<<1|1);//右儿子,即pos*2+1
	pushup(pos);//pushup维护当前区间的父亲区间的值是正确的。
	//因为build是递归实现的,所以这里会从叶子节点一层一层网上返回修改父亲节点的值 
}

void update(int L,int R,int l,int r,int pos,int v){//以区间加法为例,LR表示要更新的区间,lr表示当前节点指向区间 
	if(L<=l&&r<=R){//[l,r]∈[L,R],这时候可以更新了 
		t[pos].sum+=v*(r-l+1);//如果是"区间变成x"那就是用=就行了
		t[pos].lazy+=v;
		return;//不要忘了! 
	}
	if(r<L||R<l) return;//[l,r]∪[L,R]=空集
	pushdown(l,r,pos);//要往下更新一层,"不能再query的时候再pushdown吗?",因为之后pushup的时候这个节点就会出现假值了,细品 
	int mid = (l+r)>>1;
	update(L,R,l,mid,pos<<1,v);
	update(L,R,mid+1,r,pos<<1|1,v);
	pushup(pos);//因为pos点往下的点可能被更新了,在递归回来的时候也要更新这里的数值 
}

void query(int L,int R,int l,int r,int pos){//区间查询,思路与区间修改类似,因为不用修改所以不pushup 
	if(L<=l&&r<=R){
		ans+=t[pos].sum;
		return;
	}
	if(r<L||R<l) return;
	pushdown(l,r,pos);
	int mid = (l+r)>>1;
	query(L,R,l,mid,pos<<1);
	query(L,R,mid+1,r,pos<<1|1);
	return;
} 

int main(){
	int l,r,v;
	cin>>n; 
	build(1,n,1);//建树
	cin>>m;
	while(m--){//修改 
		scanf("%d%d%d",&l,&r,&v);
		update(l,r,1,n,1,v);
	}
	cin>>m;
	while(m--){//查询 
		scanf("%d%d",&l,&r);
		ans=0;
		query(l,r,1,n,1);
		cout<<ans<<endl;
	} 
}

主席树系列
可持久化线段树
洛谷P3834 【模板】可持久化线段树 2(主席树)
https://www.luogu.com.cn/problem/P3834
其实就是拿的大爹板子然后自己加了很多注释:
https://www.luogu.com.cn/blog/bestFy0731/solution-p3834

#include<iostream>
#include<algorithm>
using namespace std;
int cansel_sync=(ios::sync_with_stdio(0),cin.tie(0),0);
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define mid ((l+r)>>1)

const int MAXN = 2e5+5;
int n,q,m,cnt=0;
int a[MAXN],b[MAXN]; 
int T[MAXN];//记录每个时间上树的根节点 
int sum[MAXN<<5];//记录节点覆盖区间内数字的数量 
int L[MAXN<<5],R[MAXN<<5];//记录左右儿子 

int build(int l,int r){//一开始通过递归建树
	int pos = ++cnt;//建立新的节点,节点数cnt自增
	sum[pos]=0;//时间0时是没有任何数字的
	if(l<r){
		L[pos]=build(l,mid);
		R[pos]=build(mid+1,r);
	}
	return pos; 
}

int update(int pre,int l,int r,int x){//pre是在pos位置上一个时间的节点编号
	int pos = ++cnt;
	L[pos]=L[pre];R[pos]=R[pre];//pre是上个时间点在如今pos位置上的节点
	//这里相当于是把这个新的节点接到相应的位置
	sum[pos] = sum[pre]+1;//这一整条链上各点的权值都+1(叶子上加了1,上面的都受影响)
	if(l<r){//找到长度为一个数的节点就结束递归了
		if(x<=mid) L[pos] = update(L[pre],l,mid,x);
		else R[pos] = update(R[pre],mid+1,r,x);
		//判断数值x的这个节点在当前节点的左儿子还是右儿子
		//这样一直沿着这条链往下,建立新的节点。
		//因为pos节点的儿子中新建立的节点需要返回编号
	}
	return pos;
}

int query(int u,int v,int l,int r,int k){
	if(l>=r) return l;
	int x = sum[L[v]]-sum[L[u]];//计算左子树中数的数量x 
	if(x>=k) return query(L[u],L[v],l,mid,k);//x比k大,说明第k大的数在左子树中
	else return query(R[u],R[v],mid+1,r,k-x);
}

int main(){
	cin>>n>>q;
	rep(i,1,n){
		cin>>a[i];
		b[i]=a[i];
	}
	sort(b+1,b+1+n);
	m = unique(b+1,b+1+n)-b-1;//排序后可以用unique函数去重 
	T[0] = build(1,m);
	rep(i,1,n){
		int t = lower_bound(b+1,b+1+m,a[i]) - b;//找到对应的离散化后的数值
		T[i] = update(T[i-1],1,m,t);//建立新时间的根节点,并且建新的链并接上去 
	}
	while(q--){
		int u,v,k;
		cin>>u>>v>>k;//找u到v区间内第k大的数 
		int t = query(T[u-1],T[v],1,m,k); 
		cout<<b[t]<<endl;//输出对应的原值
	} 
}


树的直径

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
#include<queue>
using namespace std;

#define MAXN 50010

int n;
vector<int> e[MAXN];//邻接表 

//这个dfs直接通过船指针来传入d,是为了分别储存dx和dy; 
int dfs(int prev,int u,int *d){//prev是存储上个点的,防止走回头路,这样还直接省掉了vis数组 
	int res = u;//res存储 距离当前点距离最远的点 的编号 
	int siz = e[u].size();
	for(int i=0;i<siz;i++){
		int v = e[u][i];
		if(v!=prev){
			d[v] = d[u]+1;//标记距离 
			int now = dfs(u,v,d);
			if(d[now]>d[res]) res = now;//找到距离当前点最远的点 
		}
	}
	return res;
} 

int main(){
	int u,v;
	int dx[MAXN];//直径端点x到各点的距离 
	int dy[MAXN];//直径端点y到各点的距离,同时给找x的时候暂用 
	scanf("%d",&n);//读入节点数量,因为是树,所以边数是n-1 
	for(int i=1;i<n;i++){
		scanf("%d%d",&u,&v);
		e[u].push_back(v);
		e[v].push_back(u);//无向图 
	}
//	memset(d1,0,sizeof(d1));
//	memset(d2,0,sizeof(d2)); 
	dy[1]=0;
	int x = dfs(-1,1,dy);//注意这里的dy只是用来暂用来找x 
	dx[x]=0;
	int y = dfs(-1,x,dx);//找y
	dy[y]=0;
	dfs(-1,y,dy);//这里才是弄好真正的dy 
	cout<<dx[y]<<endl;
}

Splay
基本是抄的这个大爹的博客https://www.cnblogs.com/cjyyb/p/7499020.html
再自己加了一点注解
全部代码https://paste.ubuntu.com/p/gH5mWpz669/

#include<bits/stdc++.h>
using namespace std;

template<class T>inline void read(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;}
//int cansel_sync=(ios::sync_with_stdio(0),cin.tie(0),0);
#define ll long long
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repb(i,a,b) for(int i=(a);i>=b;i--)
#define INF 0x3f3f3f3f
#define cendl printf("\n")
ll gcd(ll a,ll b){ while(b^=a^=b^=a%=b); return a; }
//#define INF 0x7fffffff

const int MAXN = 5e5+5;
int root,tot,n;

struct Node{
    int ch[2];//左右儿子
    int val;//值
    int fa;//父节点
    int size;//子树大小
    int cnt;//计数 
}t[MAXN];

inline void pushup(int x){//维护节点的size 
	t[x].size = t[x].cnt;//自己重复的次数先累计
	if(t[x].ch[0]) t[x].size+=t[t[x].ch[0]].size;
	if(t[x].ch[1]) t[x].size+=t[t[x].ch[1]].size;
	//如果有儿子,把儿子的size加到自己
	//同线段树的pushup必须先处理儿子再处理父节点
}

void rotate(int x){//旋转操作
	int y=t[x].fa, z=t[y].fa;
	int k=(t[y].ch[1]==x);//x是y的左还是右儿子 
	t[z].ch[t[z].ch[1]==y] = x;//x换到y原来在的位置
	t[x].fa = z; 
	t[y].ch[k] = t[x].ch[k^1];//y顶下来x的儿子,放在x原来对y的位置 
	t[t[x].ch[k^1]].fa = y;
	t[x].ch[k^1] = y;//y换到 x原来在y的 相对的 位置
	t[y].fa = x;
	pushup(y),pushup(x);//更新size,因为y是儿子,所以先y 
}//这里的异或是用来调整0和1(左右儿子) 

inline void splay(int x,int goal){//找到x并把x旋转为goal的儿子根节点 
	while(t[x].fa!=goal){//x一直旋转到成为goal的儿子
		int y=t[x].fa,z=t[y].fa; 
		if(z!=goal)//如果y不是根节点,分两类讨论 
			(t[z].ch[0]==x)^(t[y].ch[0]==y)?rotate(x):rotate(y);
			//x和y分别是y和z的同一侧儿子,先转y再转x;不同则先转x转两次 
		rotate(x);//无论三种情况中的哪一种都要最后转x 
	}
	if(goal==0) root=x;//若goal是0,根节点更新为x
}

inline void find(int x){//查找x的位置,并旋转x到根节点,类似二分,O(logn) 
	int u = root;
	if(!u) return;//树是空的情况
	while(t[u].ch[x>t[u].val]&&x!=t[u].val)//找到x,不一定找得到
		u = t[u].ch[x>t[u].val];//左儿子小,右儿子大 
	splay(u,0);//当前位置旋转到根节点.根节点的fa存0
}

inline void insert(int x){//类似find,如果插入的数已经存在,可以在找到的节点计数 
	int u = root,fu = 0;//当前位置u,u的爸爸fu 
	while(u&&t[u].val!=x){//找合适位置,u找到空的地方也会停止
		fu = u;
		u = t[u].ch[x>t[u].val];
	}
	if(u) t[u].cnt++;//已有这个数字的情况,计数 
	else{
		u = ++tot;//节点总数tot+1
		if(fu)//这时候fu是上一个u即新插入u的父节点,如果父节点不是0
			t[fu].ch[x>t[fu].val]=u;
		t[u].ch[0] = t[u].ch[1] = 0;//这个新节点没儿子
		t[u].fa = fu;//父亲
		t[u].val = x;//数值
		t[u].cnt = 1;//计数
		t[u].size  = 1;//大小
	}
	splay(u,0);
}

inline int Next(int x,int f){
	find(x); 
	int u=root;//根节点,此时x的父节点(存在的话)就是根节点 
	//这里sls回答了我的疑问
	//"splay中不一定有x这个节点,那么它splay到根的就直接可以满足了"
	//"如果有这个点的话就要在splay上再找一波(因为当前的根就是x这个点)"
	if(t[u].val>x&&f)return u;//如果当前节点的值大于x并且要查找的是后继
    if(t[u].val<x&&!f)return u;//如果当前节点的值小于x并且要查找的是前驱 
    //上面两个是x不在splay中的情况
	u = t[u].ch[f];//后继在根右边找,前驱在左边找
	while(t[u].ch[f^1]) u = t[u].ch[f^1];//左半边要往右跳找最大的,右半边往左跳 
	return u;
}

inline void Delete(int x){//删除x 
	int last = Next(x,0);//查找x的前驱
	int next = Next(x,1);//找x的后继
	splay(last,0);splay(next,last);//前驱节点转到根节点,后继转到根的儿子上
	//操作完之后.后继是前驱的右儿子,x是前驱的左儿子,而且x是叶子节点 
	int del = t[next].ch[0];//后继的左儿子x
	if(t[del].cnt>1){
		t[del].cnt--;//如果有多个x,则x的计数减少一个
		splay(del,0);//这个splay还重新pushup计算了del的子树
	}
	else
		t[next].ch[0]=0;//因为是左儿子是叶子节点,直接丢掉 
}

inline int kth(int x){//找第k小,改一下也可以找第k大 
	int u=root;
		return 0; 
	while(1){
		int y = t[u].ch[0];//左儿子
		if(x>t[y].size+t[u].cnt){//如果左儿子和当前点的size比要找的排名数小 
			x-=t[y].size+t[u].cnt;//数量减少,相当于把这个寻找排名的起点变成了当前节点 
			u=t[u].ch[1];//那么当前排名的数一定要往右儿子上找 
		}
		else if(t[y].size>=x) u=y;//左儿子的size足够,儿子在左侧上找 
		else return t[u].val;//左儿子的size比x小,加上当前点u的size则比x大,说明第kA大的就是x 
	}
} 

int main(){
	tot=0;
	read(n);
    insert(+2147483647);insert(-2147483647);//博客作者在这里先加了正负INF
    int typ,x;
    while(n--){
        read(typ);
        if(typ==1){read(x);insert(x);}//插入 
        else if(typ==2){read(x);Delete(x);}//删除 
        else if(typ==3){//查找 
			read(x);find(x);
            printf("%d\n",t[t[root].ch[0]].size);
        }
        else if(typ==4){//第k小 
            read(x);printf("%d\n",kth(x+1));//之前插进去正负INF,所以要+1; 
        }
        else if(typ==5){//前驱
            read(x);printf("%d\n",t[Next(x,0)].val);
        }
        else if(typ==6){//后继 
            read(x);printf("%d\n",t[Next(x,1)].val);
        }
    }
    return 0;
}

树链剖分
洛谷
P3384 【模板】轻重链剖分

#include<iostream>
#include<algorithm>
#include<vector>

using namespace std;
#define ll long long
const int MAXN = 1e5+5;

ll ans;
int da[MAXN];//记录dfn序上的各点数值用来初始化线段树
int n,m,rt,p;//节点数,询问数,根,模数 

struct tree{
	int sum;
	int lazy;
};

struct St{//线段树 
	tree t[MAXN<<2];
	void pushup(int pos){ 
		t[pos].sum=(t[pos<<1].sum+t[pos<<1|1].sum) %p;
		return;
	}
	void pushdown(int l,int r,int pos){
		if(!t[pos].lazy) return;
		int mid = (l+r)>>1;
		t[pos<<1].sum += t[pos].lazy*(mid-l+1);
		t[pos<<1].sum %= p;//取模 
		t[pos<<1|1].sum += t[pos].lazy*(r-(mid+1)+1);
		t[pos<<1|1].sum %= p;//取模 
		t[pos<<1].lazy += t[pos].lazy;
		t[pos<<1].lazy %= p;//取模 
		t[pos<<1|1].lazy += t[pos].lazy;
		t[pos<<1|1].lazy %= p;//取模 
		t[pos].lazy = 0; 
	}
	void build(int l,int r,int pos){ 
		t[pos].sum = t[pos].lazy = 0;
		if(l==r){
			t[pos].sum = da[l];
			return;
		}
		int mid = (l+r)>>1;
		build(l,mid,pos<<1);
		build(mid+1,r,pos<<1|1);
		pushup(pos);
	}
	void update(int L,int R,int l,int r,int pos,int v){
		if(L<=l&&r<=R){
			t[pos].sum += v*(r-l+1);
			t[pos].lazy += v;
			t[pos].lazy %= p;//取模 
			return;
		}
		if(r<L||l>R) return;
		pushdown(l,r,pos);
		int mid = (l+r)>>1;
		update(L,R,l,mid,pos<<1,v);
		update(L,R,mid+1,r,pos<<1|1,v);
		pushup(pos);
	}
	void query(int L,int R,int l,int r,int pos){
		if(L<=l&&r<=R){
			ans += t[pos].sum;
			ans%=p;//取模 
			return;
		}
		if(r<L||R<l) return;
		pushdown(l,r,pos);
		int mid = (l+r)>>1;
		query(L,R,l,mid,pos<<1);
		query(L,R,mid+1,r,pos<<1|1);
		return;
	}
	//查询和修改,为了简化参数,我又写了两个 
	ll tquery(int L,int R){
		ans = 0;
		query(L,R,1,n,1);
		return ans; 	
	}
	void tupdate(int L,int R,int v){
		update(L,R,1,n,1,v);
	} 
};
//树结构 
vector<int> e[MAXN];//记录边
int a[MAXN];//记录编号对应节点的初始数值

//树剖部分
St segt;
int si[MAXN],dep[MAXN],fa[MAXN],rem[MAXN],dfn[MAXN],top[MAXN];
int dfn_num;

void dfs1(int x,int faa){//预处理出fa,dep,si,rem
	int ma = 0;//用来x的重儿子,记录最大的size
	si[x] = 1;
	for(auto v:e[x]){
		if(v==faa) continue;//跳过父亲节点 
		dep[v] = dep[x]+1;//更新儿子的dep 
		dfs1(v,x);
		si[x] += si[v];//x的size加上当前儿子的size
		fa[v] = x;//标记v的父节点为x 
		if(si[v]>ma){
			ma = si[v];
			rem[x] = v;//记录重儿子 
		} 
	}
} 

void dfs2(int x,int faa){//预处理出dfn,top
	if(rem[faa]==x) top[x] = top[faa];//同一条重链同一个top 
	else top[x] = x;//否则为重链头
	dfn[x] = ++dfn_num;//更新树剖序,同时下标自增
	da[dfn_num] = a[x];
	if(rem[x]) dfs2(rem[x],x);//优先遍历重儿子
	for(auto v:e[x]){
		if(v==faa) continue;
		if(v==rem[x]) continue;//重儿子之前已经遍历过了
		dfs2(v,x); 
	} 
}

inline ll cal(int L,int R){
	return segt.tquery(L,R);
}

void init(){//初始化
	dfn_num=0;
	dfs1(rt,0);
	dfs2(rt,0);
	segt.build(1,n,1);//这里通过da来初始化线段树 
}

ll query(int x,int y){
	ll res=0;
	while(top[x]!=top[y]){//跳到同一条重链 
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		//重链深度更大的点优先往上跳
		res += cal(dfn[top[x]],dfn[x]);//算上这条重链的 
		res%=p;//取模 
		x = fa[top[x]];//跳到重链头的父节点上 
	}
	//跳出这个循环时,xy已经在同一个重链上了
	res += cal(min(dfn[x],dfn[y]),max(dfn[x],dfn[y]));
	res%=p;//取模 
	//xy不确定顺序对不对,所以取minmax 
	return res;
}

void update(int x,int y,int v){
	ll res=0;
	while(top[x]!=top[y]){//跳到同一条重链 
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		//重链深度更大的点优先往上跳
		segt.tupdate(dfn[top[x]],dfn[x],v);//更新数值 
		x = fa[top[x]];//跳到重链头的父节点上 
	}
	//跳出这个循环时,xy已经在同一个重链上了
	segt.tupdate(min(dfn[x],dfn[y]),max(dfn[x],dfn[y]),v);
	//xy不确定顺序对不对,所以取minmax
}

int main(){
	cin>>n>>m>>rt>>p;//节点数,操作数,根节点序号,模数
	for(int i=1;i<=n;i++){
		cin>>a[i];//记录初始数值 
		a[i] = a[i]%p;
	} 
	int x,y;
	for(int i=1;i<=n;i++) e[i].clear();
	for(int i=1;i<n;i++){
		cin>>x>>y;
		e[x].push_back(y);
		e[y].push_back(x);
	}
	init();
	int v;
	int typ;
	while(m--){
		cin>>typ;
		if(typ==1){//简单路径上修改 
			cin>>x>>y>>v;
			update(x,y,v);
		}
		else if(typ==2){//简单路径上查询 
			cin>>x>>y;
			cout<<query(x,y)<<endl;
		}
		else if(typ==3){//子树上修改 
			cin>>x>>v;
			segt.tupdate(dfn[x],dfn[x]+si[x]-1,v);
		}
		else if(typ==4){//子树上查询 
			cin>>x;
			cout<<segt.tquery(dfn[x],dfn[x]+si[x]-1)<<endl;
		}
	}
} 

LCA
倍增法
(这里用的是HDOJ4547的代码

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
#include<queue>
#include<map>
using namespace std;
int cansel_sync=(ios::sync_with_stdio(0),cin.tie(0),0);
#define log(x) (31-__builtin_clz(x))//谢谢hjt
const int MAXN = 1e6+5;
constexpr int LOGN = log(MAXN)/log(2)+5;

int z,m,n;
int fa[MAXN],p[MAXN][LOGN];
int L[MAXN];//记录深度 
int lg[MAXN];
vector<int> e[MAXN];

void init_lg(){//预处理lg,给之后计算省时间 
	lg[1]=0;
	for(int i=2;i<MAXN;i++) lg[i]=lg[i-1]+( (1<<(lg[i-1]+1))==i );
} 

void dfs(int x){
	for(auto u:e[x]){
		L[u]=L[x]+1;
		dfs(u);
	}
}

void preprocess(int n){
	for(int i=1;i<=n;i++)
		for(int j=0;1<<j<=n;j++) p[i][j]=-1;//有些倍增后出界的,用-1标记 
	for(int i=1;i<=n;i++)
		p[i][0]=fa[i];//倍增长度为1的时候指向格子的父节点
	for(int j=1;1<<j<=n;j++)
		for(int i=1;i<=n;i++)
			if(p[i][j-1]!=-1) p[i][j]=p[p[i][j-1]][j-1];//两端长度加起来正好是两倍即次数+1 
}

int LCA(int u,int v){
	if(L[u]<L[v]) swap(u,v);//u成为离根节点更远的
	int log = lg[L[u]];//找到L[u]的二进制最高位 
	for(int i=log;i>=0;i--)
		if(L[u]-(1<<i)>=L[v]) u=p[u][i];//u往上爬到与v同高
	if(u==v) return u;
	for(int i=log;i>=0;i--)
		if(p[u][i]!=-1&&p[u][i]!=p[v][i]){//找公共祖先,逼近它但是又不超过它 
			u=p[u][i]; v=p[v][i]; 
		} 
	return fa[u];
} 

int main(){
	init_lg();//预处理log2x,给之后的计算省时间 
	int z,q,u,v;
	string aa,bb;
	cin>>z;
	while(z--){
		map<string,int>mp;
		cin>>n>>q;
		if(n==1){
			while(q--){
				cin>>aa>>bb;cout<<0<<endl;
			}
			continue;
		}
		int countt = 0;
		for(int i=1;i<=n;i++){
			fa[i]=i;
		}
		for(int i=1;i<n;i++){
			cin>>aa>>bb;
			int &a = mp[aa];int &b = mp[bb];
			if(!a) a=++countt;
			if(!b) b=++countt;
			fa[a]=b;
			e[b].push_back(a);
		}
		int rt;
		for(int i=1;i<=n;i++){
			if(fa[i]==i) {
				rt=i;break;
			}
		}
		L[rt]=1;
		dfs(rt);
		preprocess(n);
		int res;
		while(q--){
			cin>>aa>>bb;
			int &a = mp[aa];int &b = mp[bb];
			int lc = LCA(a,b);
			res = L[a]-L[lc];
			if(lc!=b) res++;
			cout<<res<<endl;
		}
		for(int i=1;i<=n;i++) e[i].clear();
	}
}

图论
最小生成树Kruskal
P3366 【模板】最小生成树 https://www.luogu.com.cn/problem/P3366

#include<iostream>
#include<algorithm>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define ll long long
using namespace std;
const int MAXN = 5050;
const int MAXM = 2e5+5;
struct Edge{
    int u,v,k;
    friend bool operator < (Edge a,Edge b){
        return a.k<b.k;
    }
}edges[MAXM];
int fa[MAXN];
int rankk[MAXN];//树高优化并查集
int find(int x){
    if(fa[x]==x) return x;
    return fa[x] = find(fa[x]);
}
void merge(int a,int b){
    a = find(a);b = find(b);
    if(a==b) return;
    if(rankk[a]>rankk[b]) fa[b] = a;
    else{
        fa[a] = b;
        if(rankk[a]==rankk[b])rankk[b]++; 
    }
}
int n,m;
int main(){
    cin>>n>>m;
    rep(i,1,m) cin>>edges[i].u>>edges[i].v>>edges[i].k;
    rep(i,1,n) fa[i] = i,rankk[i] = 0;
    sort(edges+1,edges+1+m);
    ll res=0;
    rep(i,1,m){
        if(find(edges[i].u)!=find(edges[i].v)){//检查两点是否在同一集合内
            res+=edges[i].k;
            merge(edges[i].u,edges[i].v);
        }
    }
    bool flag = 1;
    rep(i,2,n){
        if(find(n)!=find(n-1)) {flag = 0;break;}
    }
    if(!flag) cout<<"orz"<<endl;
    else cout<<res<<endl;
}

二分图最大匹配 匈牙利算法

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;

#define MAXN 10000

int uN,vN;//u,v的数目
bool g[MAXN][MAXN];//邻接矩阵,一般情况下uv最大范围相同 
int linker[MAXN];//存右节点的对象 
bool used[MAXN]; //右点是否访问过

bool dfs(int u){
	for(int v=0;v<vN;v++){
		if(g[u][v]&&!used[v]){//判断是否有边,是否被用过
			used[v]=true;//标记访问
			if(linker[v]==-1||dfs(linker[v])){
				linker[v]=u;//很精妙,记录新配对,在进行dfs后,连接左右节点的边其实方向反了一反 
				return true; 
			} 
		}
	}
	return false; 
} 

int hungary(){//匈牙利算法 
	int res=0;
	memset(linker,-1,sizeof(linker));//初始化,因为节点uv从0开始,所以linker初始化为-1 
	for(int u=0;u<uN;u++){
		memset(used,false,sizeof(used));
		if(dfs(u)) res++; 
	} 
	return res;
}

int main(){
	int e;
	cin>>uN>>vN>>e;
	int u,v;
	memset(g,0,sizeof(g));
	while(e--){
		scanf("%d%d",&u,&v);
		g[u][v]=1;
	}
	cout<<hungary()<<endl;
}
 
/*匈牙利算法可以解决的问题:
1.二分图的最大匹配数(如婚配问题)
2.最小顶点覆盖------用最少的点覆盖所有的边(如HDOJ禁止早恋,任务安排)
	结论:最小顶点覆盖数==最大匹配数量 
3. DAG(有向无环图)的最小路径覆盖
	(如HDOJ空袭,所有路单行,并且所有街是两个路口相连,已知不会形成回路。问最少空降几个伞兵可以访问所有路口)
	拆点法:1拆成1和1',数字x放左节点,x'放右节点
	DAG图的最小路径覆盖数 == 节点数(n) - 最大匹配数 
*/ 

优先队列优化Dijkstra

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<cmath>
using namespace std;

template<class T>inline void read(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;}
int cansel_sync=(ios::sync_with_stdio(0),cin.tie(0),0);
#define ll long long
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repb(i,a,b) for(int i=(a);i>=b;i--)
#define INF 0x3f3f3f3f
#define cendl printf("\n")
ll gcd(ll a,ll b){ while(b^=a^=b^=a%=b); return a; }
//#define INF 0x7fffffff
#define LINF 1ll<<60
const int MAXN = 2e5+10;
typedef pair<int,ll> pli;
vector<pli> e[MAXN];//first是目标的点,second是距离dis 
bool vis[MAXN];
ll d[MAXN];
int n,m,s,t;

void dijkstra(){
	rep(i,1,n){
		d[i] = LINF;
		vis[i] = 0;
	}
	d[s] = 0;
	priority_queue< pli,vector<pli>,greater<pli> >q;//first存d[x],second存x的编号 
	q.push(make_pair(0,s));
	while(!q.empty()){//进行类似bfs的操作 
		int now = q.top().second;
		q.pop();
		if(vis[now])continue;//可以看到下面的操作是都先推进去的,所以可能重复遇到now点 
		vis[now] = 1;
		int siz = e[now].size();
		for(auto x:e[now]){//遍历now的所有边 
			int v = x.second;//到达的点
			if(d[v]>d[now]+x.first){
				d[v] = d[now] + x.first;
				q.push(make_pair(d[v],v));//推入优先队列 
			} 
		}
	}
}

int main(){
	cin>>n>>m>>s;
	int u,v;
	ll dis;
	rep(i,1,m){
		cin>>u>>v>>dis;
		e[u].push_back(make_pair(dis,v));
		//e[v].push_back(make_pair(u,dis));//双向通行的情况 
	}
	dijkstra();
	rep(i,1,n){
		cout<<d[i];//输出到各个点的最短路径 
		if(i!=n) cout<<' ';
	}
} 

拓扑排序
Uva10305
https://vjudge.net/problem/UVA-10305

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<cmath>
using namespace std;

template<class T>inline void read(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;}
int cansel_sync=(ios::sync_with_stdio(0),cin.tie(0),0);
#define ll long long
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repb(i,a,b) for(int i=(a);i>=b;i--)
#define INF 0x3f3f3f3f
#define cendl printf("\n")
ll gcd(ll a,ll b){ while(b^=a^=b^=a%=b); return a; }
//#define INF 0x7fffffff

const int MAXN = 110;

vector<int> e[MAXN];
int n,m;
int indg[MAXN];

void solve(){
	for(int i=1;i<=n;i++) {indg[i]=0;e[i].clear();}
	for(int i=1,u,v;i<=m;i++){
		cin>>u>>v;
		e[u].push_back(v);
		indg[v]++;
	}
	queue<int> q;
	for(int i=1;i<=n;i++)
		if(indg[i]==0) q.push(i);//找到入度为0的点
	vector<int> res;//储存结果的向量
	int u;
	while(!q.empty()){//BFS
		u = q.front();
		q.pop();
		res.push_back(u);
		for(auto v:e[u]){
			if(--indg[v]==0)q.push(v);
			//删掉当前点u,点v的入度--
			//这一步的目的是找到新的入度为0的节点,推入队列Q
		}
	}
	if(res.size()==n){//如果这个图无环的话,说明可以进行拓扑排序
		for(int i=0;i<n;i++){//输出结果
			cout<<res[i]<<' ';
		}
		cout<<endl;
	}
}

int main(){
	while(cin>>n>>m&&!(n==0&&m==0)) solve();
}

Tarjan求强连通分量
洛谷P2835 刻录光盘
https://www.luogu.com.cn/problem/P2835#submit

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<cmath>
using namespace std;

template<class T>inline void read(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;}
int cansel_sync=(ios::sync_with_stdio(0),cin.tie(0),0);
#define ll long long
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repb(i,a,b) for(int i=(a);i>=b;i--)
#define INF 0x3f3f3f3f
#define cendl printf("\n")
ll gcd(ll a,ll b){ while(b^=a^=b^=a%=b); return a; }
//#define INF 0x7fffffff

const int MAXN = 2e6+5;

int n;
vector<int> e[MAXN];//存图 
int dfn[MAXN],low[MAXN];
int dfncnt;//dfn自增下标 
int s[MAXN],tp;//数组模拟栈,tp记录大小 
bool in[MAXN];//记录该点是否在栈中 
int scc[MAXN],sc;//节点i所在scc的编号,sc记录有几个强连通 
int sz[MAXN];//强连通i的大小 
int indg[MAXN];//记录缩点后的入度(这题才有的 

void tarjan(int u){
	low[u]=dfn[u]=++dfncnt;//low初始值为自身dfn
	s[++tp]=u;//推u入栈,从1开始 
	in[u]=1;//记录u点在栈中 
	for(auto v:e[u]){//访问到新点的情况 
		if(!dfn[v]){
			tarjan(v);
			low[u] = min(low[u],low[v]);//用low[v}更新low[u] 
		}
		else if(in[v])//v被访问过,但是在栈中 
			low[u] = min(low[u],dfn[v]);
	}
	if(dfn[u]==low[u]){//u是连通分量的根节点
		sc++;//强连通数量++
		sz[sc] = 0; 
		while(s[tp]!=u){//u和u之后的点全部出栈 
			scc[s[tp]] = sc;//这个点包含于第几个强连通
			sz[sc]++;//u为根的这个强连通的大小 
			in[s[tp]] = 0;//出栈 
			tp--; 
		}
		scc[u] = sc;//给根节点标,属于第sc个强连通 
		sz[sc]++;
		in[u] = 0;
		tp--;
	}
} 

void reset(){
	tp = sc = dfncnt =0;
	rep(i,1,n){
		in[i] = dfn[i] = 0;//low不用清空,sz在之后用到再清空 
		e[i].clear();
	}
}

int main(){
	cin>>n;
	reset();
	int v;
	rep(u,1,n){
		while(cin>>v&&v!=0) e[u].push_back(v);
	} 
	rep(u,1,n)
		if(!dfn[u]) tarjan(u);
	rep(i,1,sc) indg[i] = 0;//这个不包含在tarjan里面,是这题记录入度的 
	rep(u,1,n){
		for(auto v:e[u]){
			if(scc[u]!=scc[v]) indg[scc[v]]++;
		}
	}
	int res = 0;
	rep(i,1,sc){
		if(indg[i]==0) res++;
	}
	cout<<res<<endl;
}

网络流Dinic
P3376 【模板】网络最大流 https://www.luogu.com.cn/problem/P3376

#include<iostream>
#include<queue>
#include<vector>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;

#define MAXN 210
#define INF 0x7fffffff
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define ll long long

struct Edge{
	int from,to;
	ll cap,flow;
};

int n,m,s,t;//节点数,边数(含反向弧),源点与汇点编号 
vector<Edge> edges;//边长,edges[e]和edges[e^1]互为反向弧 
vector<int> G[MAXN];//邻接表,G[i][j]表示节点i的第j条边在edges中的编号
int d[MAXN];//dist层数,即d[i]为起点到i的距离(层数),同时也起到vis的作用防止重复走
int cur[MAXN];//这个cur是dfs的时候用来省略重复步骤的

void add_edge(int from,int to,ll cap){
	edges.push_back((Edge){from,to,cap,0});
	edges.push_back((Edge){to,from,0,0});
	int siz = edges.size();
	G[from].push_back(siz-2);//正向边
	G[to].push_back(siz-1);//反向边
}

bool bfs(){
    memset(d,-1,sizeof(d));
	queue<int> q;
	q.push(s);
	d[s]=0;//dist(s) = 0
	while(!q.empty()){
		int x=q.front();
		q.pop();
		rep(i,0,G[x].size()-1){
			Edge &e = edges[G[x][i]];
			if(d[e.to]==-1&&e.cap>e.flow){//只考虑残量网络中的弧即这个边还没被填满
				d[e.to]=d[x]+1;//标记层次dist(x)
				q.push(e.to);
			}
		}
	}
	return d[t]!=-1;//找到到t节点的路径则回1,找不到则回0
} 

ll dfs(int x,ll a){//DFS除了当前节点x外,还要传入"目前为止所有边的最小残量"即"水流到这里还剩多少"
	if(x==t||a==0) return a;//也是很简洁一句话 ,结束增广 
 	ll flow = 0,f;
	for(int &i=cur[x];i<G[x].size();i++){//上一次阻塞流已经把cur[x]之前的弧都排除了 
		Edge &e = edges[G[x][i]];
		if(d[x]+1==d[e.to]&&(f=dfs(e.to,min(a,e.cap-e.flow)))>0){
			e.flow+=f;
			edges[G[x][i]^1].flow-=f;
			flow+=f;
			a-=f;
			if(a==0) break;//这句及时退出很影响效率 
		} 
	}
	return flow; 
}

ll Maxflow(){
	ll flow=0;
	while(bfs()){
		memset(cur,0,sizeof(cur));//因为有新的反向边引入,即"正向边"更新了 ,有些实际上是反,但dfs里面当正的用 
		flow+=dfs(s,INF);//对当前阻塞流dfs; 
	}
	return flow;
} 
int main(){
	int u,v;
	ll cap;
	scanf("%d%d%d%d",&n,&m,&s,&t);
	for(int i=1;i<=m;i++){
		scanf("%d%d%lld",&u,&v,&cap);
		add_edge(u,v,cap);
	}
	cout<<Maxflow()<<endl;
}

字典树Trie
字典树Trie

#include<iostream>
#include<cstring>
using namespace std;

#define MAXN 10000
const int maxnode=2e6+5;
const int sigma_size=26;//字母开26,如果是数字之类的则开10 

char s[MAXN];

struct Trie{
	int ch[maxnode][sigma_size];
	int val[maxnode];
	int sz; 
	
	Trie(){sz=1;memset(ch[0],0,sizeof(ch[0]));}//初始化,此时只有根节点
	
	int idx(char c){
		return c-'a';//同理如果是数字则换成c-'0' 
	} 
	
	void insert(char *s,int v){
		int u=0,n=strlen(s);
		for(int i=0;i<n;i++){
			int c=idx(s[i]);
			if(!ch[u][c]){//节点不存在(u没有子节点字母为c)这个u和c的意义全然不同,u是节点编号,c是字符编号 
				memset(ch[sz],0,sizeof(ch[sz]));
				val[sz]=0;//中间节点,附加信息为0,当然也可以用其他的来标记比如-1 
				ch[u][c]=sz++;//画好边,并且总结点数++ 
			}
			u=ch[u][c];//光标往下走 
		}//这里出来就是u已经到单词节点(即一个单词的末尾字符)上了 
		val[u] = v;//insert时的第二个参数v,可以附加信息 
	} 
	bool find(char *s,int len){//查找串s长度不超过len的前缀,改一改成int也可以返回附加值 
		int u=0;
		for(int i=0;i<len;i++){
			if(s[i]=='\0') break;
			int c=idx(s[i]);
			if(!ch[u][c]) break;
			u=ch[u][c];
			if(val[u]!=0) return true;
		} 
		return false; 
	} 
}T;//之前因为T定义在main里面所以一直运行不了,发现这个结构体和数组有点类似
//定义为全局函数可以开得更大! 
int main(){
	int n;
	scanf("%d",&n);
	getchar();
	Trie T;
	while(n--){
		scanf("%s",s);
		getchar();
		T.insert(s,1);
	}
	scanf("%d",&n);
	getchar();
	while(n--){
		scanf("%s",s);
		getchar();
		if(T.find(s,strlen(s))) cout<<"yes!"<<endl;
		else cout<<"no!"<<endl;
	}
}

RMQ
ST(Sparse Table)法

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
#include<queue>
#include<cmath>
using namespace std;

const int MAXN = 1e5+10;
const int LOGN = log(MAXN)/log(2); 

int M[MAXN][LOGN]; 
int a[MAXN];
int z,m,n;

void init(){//初始化,复杂度O(nlogn) 
	for(int i=1;i<=n;i++) M[i][0]=i;//长度为1的区间最值是自己 
	for(int j=1;j<=LOGN;j++){
		for(int i=1;i<=n-(1<<j)+1;i++){
			if(a[M[i][j-1]]<a[M[i+(1<<(j-1))][j-1]]) M[i][j] = M[i][j-1];//这里以最小值为例 
			else M[i][j] = M[i+(1<<j-1)][j-1];
		}
	} 
}

int query(int l,int r){
	int k = log(r-l+1)/log(2);//向下取整
	if(a[M[l][k]]<a[M[r-(1<<k)+1][k]]) return M[l][k];
	else return M[r-(1<<k)+1][k];
}

int main(){
	int q;
	scanf("%d%d",&n,&q); 
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
	}
	init();
	int l,r;
	while(q--){
		scanf("%d%d",&l,&r);
		printf("%d\n",a[query(l,r)]);
	}
}

数学
高斯消元解方程组
洛谷P3389 【模板】高斯消元法
https://www.luogu.com.cn/problem/P3389

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;

#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repb(i,a,b) for(int i=(a);i>=b;i--)

const int MAXN = 1004; 
double a[MAXN][MAXN];
int n,pl;

int main(){
	scanf("%d",&n);
	rep(i,1,n)
		rep(j,1,n+1) scanf("%lf",&a[i][j]); 
	rep(i,1,n){
		pl = i;
		while(a[pl][i]==0&&pl<=n) pl++;//找到每列的第一个非0元素
		if(pl==n+1){//无解的情况(存在空列 
			cout<<"No Solution"<<endl;return 0;
		} 
		rep(j,1,n+1) swap(a[i][j],a[pl][j]);//保证i行i列必不是0
		double k = a[i][i];//第二步,使a[i][i]变成1 
		rep(j,1,n+1) a[i][j]/=k;//i行所有元素除a[i][i]
		rep(ii,1,n){
			if(ii==i) continue;//枚举不同的两行
			double ki = a[ii][i];
			rep(m,1,n+1) a[ii][m]-=ki*a[i][m]; 
		}  
	}
	rep(i,1,n) printf("%.2lf\n",a[i][n+1]);
}

数学
Olog求组合数

#include<iostream>
using namespace std;
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define ll long long

const int MAXN = 3e5+5;
const int med = 998244353;
ll jc[MAXN];
ll qpow(ll d,ll c){//快速幂
    ll res = 1;
    while(c){
        if(c&1) res=res*d%med;
        d=d*d%med;c>>=1;
    }return res;
}
inline ll niyuan(ll x){return qpow(x,med-2);}
void initjc(){//初始化阶乘
    jc[0] = 1;
    rep(i,1,MAXN-1) jc[i] = jc[i-1]*i%med;
}
inline int C(int n,int m){//n是下面的
    if(n<m) return 0;
    return jc[n]*niyuan(jc[n-m])%med*niyuan(jc[m])%med;
}
int main(){
    initjc();
    int n,m;
    while(cin>>n>>m) cout<<C(n,m)<<endl;
}

数论
扩展欧几里得定理Exgcd(常用于求ax+by=gcd(a,b)的一组可行解

//OIWIKI里好理解的版本
Exgcd(int a,int b,int &x,int &y){
	if(!b){x=1;y=0;return a;}//边界条件结束递归 
	int d = Exgcd(b,a%b,x,y);//gcd 
	int t = x;
	x=y;y=t-(a/b)*y;//通过x2y2求得x1y1,层层返回 
	return d;
}
//紫书里面刘汝佳的简短的版本
Exgcd(int a,int b,int &d,int &x,int &y){//不同的是,这里的d使用引用来实现 
	if(!b){d=a;x=1;y=0;}
	else{Exgcd(b,a%b,d,y,x);y-=x*(a/b);}//先交换了xy的位置,实现y1=x2-(a/b)*x2 
} 

欧拉函数(单个数n)

int euler_phi(int n){
	int sqr = sqrt(n+0.5);
	int res = n; 
	for(int i=2;i<=sqr;i++){
		if(n%i==0){//找到一个质因子 
			res = res/i*(i-1);//先除后乘,防止越界 
			while(n%i==0) n/=i;//把这个因子从n中消除掉 
		}
	}
	if(n>1) res = res/n*(n-1);//大于sqrt的因子最多只有一个 
	return res; 
}

线性求乘法逆元

void init(int p){
	inv[1] = 1;
	for(int i=2;i<=n;i++){
		inv[i] = (ll)(p-p/i)*inv[p%i]%p;
	}
}

扩展中国剩余定理
洛谷P4777 【模板】扩展中国剩余定理(EXCRT)
https://www.luogu.com.cn/problem/P4777

#include<iostream>
using namespace std;
#define ll __int128
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
template<class T>inline void read(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>
void wt(T x){//快写
   if(x < 0) putchar('-'), x = -x;
   if(x >= 10) wt(x / 10);
   putchar('0' + x % 10);
}
void Exgcd(ll a,ll b,ll &d,ll &x,ll &y){//扩展欧几里得
	if(!b){d=a;x=1;y=0;}
	else{Exgcd(b,a%b,d,y,x);y-=x*(a/b);}//先交换了xy的位置,实现y1=x2-(a/b)*x2 
}
inline ll mul(ll a,ll b,ll mo){//取模乘法
    return ((a%mo)*(b%mo)%mo+mo)%mo; 
}
const int MAXN = 1e5+5;
ll c[MAXN],m[MAXN];//每组同余式的余数和模数
int n;
ll Excrt(ll m[],ll c[],int n){
    ll mnow = m[1],cnow = c[1];//记录每次合成后的模数余数
    rep(i,2,n){
        ll p1,p2,gcdd;
        ll m1 = mnow,m2 = m[i];//m1p1+c1 = m2p2+c2
        ll dc = (c[i]-cnow%m2+m2)%m2;//dc在保证同余的情况下变成最小的正数
        Exgcd(m1,m2,gcdd,p1,p2);
        if(dc%gcdd) {cout<<i<<endl;return -1;}
        p1 = mul(p1,dc/gcdd,m2/gcdd);//p1存的实际是p1*m1,这里的模数比较讲究
        //一会儿要对lcm(m1,m2)取模,最终结果是[p1*m1*(dc/gcdd)] % [m2/gcdd*m1]
        //m1还没乘上去,这时候先对m2/gcdd取模
        cnow += p1*m1;//更新cnow和mnow
        mnow = m1/gcdd*m2;
        cnow = (cnow%mnow+mnow)%mnow;
    }
    return cnow;  
} 
int main(){
    cin>>n;
    rep(i,1,n) read(m[i]),read(c[i]);
    wt(Excrt(m,c,n));
}
//洛谷P4777 【模板】扩展中国剩余定理(EXCRT)
//https://www.luogu.com.cn/problem/P4777

计算几何
三点算圆心
来源:https://blog.csdn.net/MallowFlower/article/details/79919797?utm_source=blogxgwz2

struct point{
	double x;
	double y;
};

point cal(point a,point b,point c){
	double x1 = a.x;double y1 = a.y;
	double x2 = b.x;double y2 = b.y;
	double x3 = c.x; double y3 = c.y;
	double a1 = 2*(x2-x1); double a2 = 2*(x3-x2);
	double b1 = 2*(y2-y1); double b2 = 2*(y3-y2);
	double c1 = x2*x2 + y2*y2 - x1*x1 - y1*y1;
	double c2 = x3*x3 + y3*y3 - x2*x2 - y2*y2;
	double rx = (c1*b2-c2*b1)/(a1*b2-a2*b1);
	double ry = (c2*a1-c1*a2)/(a1*b2-a2*b1);
	return point{rx,ry};
}

母函数
最普通的母函数

#include<iostream>
using namespace std;
#define rep(i,a,b) for(int (i)=a;i<=b;i++)

const int MAXN = 1e4;
int c1[MAXN+1];
int c2[MAXN+1];

int main(){
	int n;
	while(cin>>n){ 
		for(int i=0;i<=n;i++){//初始化 
			c1[i]=0;
			c2[i]=0;
		}
		for(int i=0;i<=n;i++){
			c1[i]=1;//面值为一元的
		}
		for(int i=2;i<=n;i++){//枚举邮票的面值(一共有几组括号 
			for(int j=0;j<=n;j++){//枚举左边次数为0到次数为n的项 
				for(int k=0;j+k<=n;k+=i){//右边的乘过来,枚举放几枚
				//次数大于n的就不用管了 
					c2[j+k]+=c1[j];
				}
			}
			for(int i=0;i<=n;i++){//最左边两个括号完成处理 
				c1[i]=c2[i];//把c2算出来的值挪到左边作为下一次的左边 
				c2[i]=0;//清空c2记录下一次括号相乘的结果 
			}
		}
		cout<<c1[n]<<endl; 
	} 
} 

单调队列
普通的单调队列,区间最大最小值
O(n) 洛谷 P1886 滑动窗口 /【模板】单调队列

#include<iostream>
#include<deque>
using namespace std; 

struct node{//结构体,存储数值和位置 
	int data;
	int order;//原本数列里的位置 
};

const int MAXN = 1e6+5;

deque<node>dq_min;
deque<node>dq_max;

int n,k;
int a[MAXN];
int res_min[MAXN];
int res_max[MAXN];

int main(){
	cin>>n>>k;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1;i<=n;i++){
		//如果队列头超过这k的范围,则出队 
		if(!dq_max.empty()&&dq_max.front().order<i-k+1) dq_max.pop_front();
		if(!dq_min.empty()&&dq_min.front().order<i-k+1) dq_min.pop_front();
		//新元素从队尾插进来,队尾没用的元素在这里出队 
		while(!dq_max.empty()&&dq_max.back().data<=a[i]) dq_max.pop_back();
		dq_max.push_back(node{a[i],i});
		while(!dq_min.empty()&&dq_min.back().data>=a[i]) dq_min.pop_back();
		dq_min.push_back(node{a[i],i});
		//存储区间最大最小值 
		res_max[i] = dq_max.front().data;
		res_min[i] = dq_min.front().data;
	}
	for(int i=k;i<=n;i++){
		cout<<res_min[i];
		if(i!=n) cout<<' ';
	}
	cout<<endl;
	for(int i=k;i<=n;i++){
		cout<<res_max[i];
		if(i!=n) cout<<' ';
	}
	cout<<endl;
}

自己乱写的二维矩阵的子矩阵最大值之和的单调队列

deque<node> dq;//每行进行单调队列
	//本来想建maxn个deque的,但是这里发现可以滚动
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(!dq.empty()&&dq.front().order<j-k+1) dq.pop_front();
			while(!dq.empty()&&a[i][j]>=dq.back().data) dq.pop_back();
			dq.push_back(node{a[i][j],j});
			res[i][j] = dq.front().data;
		}
		dq.clear();
	}
	//对k开始的每列进行单调队列 
	for(int j=k;j<=m;j++){
		for(int i=1;i<=n;i++){
			if(!dq.empty()&&dq.front().order<i-k+1) dq.pop_front();
			while(!dq.empty()&&dq.back().data<=res[i][j]) dq.pop_back();
			dq.push_back(node{res[i][j],i});
			res[i][j] = dq.front().data;
		}
		dq.clear();
	}
	ll msum = 0;
	for(int i=k;i<=n;i++){
		for(int j=k;j<=m;j++){
			msum+=res[i][j];
		}
	}
	cout<<msum<<endl;

字符串
KMP

//OIWIKI
const int MAXN = 2e6+5;
int pi[MAXN];//MAXN记得开大一点,因为这里要存到m+n+1长度的 
vector<int> res;//储存答案
 
void getpi(const string &s){ //求s的前缀函数
	pi[0]=0;
	int j=0;
	rep(i,1,s.length()-1){
		while(j>0&&s[i]!=s[j]) j=pi[j-1];//找到合适且最长的j 
		if(s[i]==s[j])j++;//能成功匹配的情况 
		pi[i]=j;
	}
}

void kmp(string s,string t){ //在主串t中找模式串s 
	getpi(s+'#'+t);
	int n=(int)s.length(),m=(int)t.length();
	rep(i,n+1,m+n+1-1)
		if(pi[i]==n) res.push_back(i-2*s.size()); //i-2n计算得左端点 
}
//Next数组的版本
int Next[10010];

void kmp_pre(char x[],int m,int Next[]){
    int i,j;
    j=Next[0]=-1;
    i=0;
    while(i<m){
        while(-1!=j&&x[i]!=x[j])j=Next[j];//判断j是否等于-1,如果回跳到第一个字符就不用再回跳
		Next[++i]=++j;
    }
}

void preKMP(char x[],int m,int kmpNext[]){
    int i,j;
    j=kmpNext[0]=-1;
    i=0;
    while(i<m){
        while(-1!=j&&x[i]!=x[j])j=kmpNext[j];
        if(x[++i]==x[++j])kmpNext[i]=kmpNext[j];
        else kmpNext[i]=j;
    }
}

int KMP_Count(char x[],int m,char y[],int n){//x是模式串,y是主串
    int i,j;
    int ans=0;
    //preKMP(x,m,next);
    kmp_pre(x,m,Next);
    i=j=0;//j表示当前已经匹配完的模式串的最后一位的位置
    while(i<n){
        while(-1!=j&&y[i]!=x[j])j=Next[j];
        //如果失配,不断向回跳,直到可以继续匹配
        i++;j++;
        //如果匹配成功,对应的模式串的位置++
        if(j>=m){
            //cout<<i-m+1<<endl; 输出匹配位置
            ans++;
            j=Next[j];
        }//继续匹配
    }
    return ans;
}

字符串哈希
洛谷P3370 【模板】字符串哈希
https://www.luogu.com.cn/problem/P3370
写了一个双哈希的

#include<iostream>
#include<algorithm> 
using namespace std;

int cansel_sync=(ios::sync_with_stdio(0),cin.tie(0),0);
#define ull unsigned long long
#define rep(i,a,b) for(int i=(a);i<=(b);i++)

typedef pair<ull,ull> pll;
const int MAXN = 1e4+5;
const int M1 = 1e9+7;//第一个模数 
const int M2 = 1e9+9;//第二个模数 
const int b = 131;
int n;
pll a[MAXN];

pll gethash(string s){
	ull res1=0,res2=0;
	int siz = s.length();
	rep(i,0,siz-1){
		res1=(res1*b%M1+s[i])%M1;//i位乘以b^i
		res2=(res2*b%M2+s[i])%M2;//f(s)=Σ s[i] * b^i;
	}
	return make_pair(res1,res2);
}

int main(){
	cin>>n;
	string s;
	rep(i,1,n){
		cin>>s;a[i]=gethash(s);
	}
	sort(a+1,a+n+1);
	int res = 1;
	rep(i,2,n){
		if(a[i]!=a[i-1]) res++;
	}
	cout<<res<<endl;
}

子串O(1)取哈希值
AcWing841

#include<iostream> 
using namespace std;
const int b = 131;
const int MAXN = 1e5 + 5;
typedef unsigned long long ull;
ull h[MAXN], pw[MAXN]; // h[k]存储字符串前k个字母的哈希值, pw[k]存储 b^k mod 2^64
//这里的模数M取的就是ull的上限2^64
char str[MAXN];

void init(int n){//初始化 
    pw[0] = 1;
    for (int i = 1; i <= n; i ++ ) {
        h[i] = h[i-1]*b + str[i];//做每个前缀的哈希值 
        pw[i] = pw[i-1]*b;//预处理b^k的值 
    }
}
// 计算子串 str[l ~ r] 的哈希值
ull get(int l, int r) {
    return h[r] - h[l-1]*pw[r-l+1];
}
int main() {
    int n, m;
    scanf("%d%d%s",&n,&m,str+1);//这样读入字符串第一位从1开始 
    init(n);
    while (m--) {
        int l1,r1,l2,r2;
        scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
        if(get(l1,r1)!=get(l2,r2))
        	printf("No\n");
        else printf("Yes\n");
    }
    return 0;
}

AC自动机
P3808 【模板】AC自动机(简单版)https://www.luogu.com.cn/problem/P3808
https://www.luogu.com.cn/blog/juruohyfhaha/solution-p3808

#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
int cansel_sync=(ios::sync_with_stdio(0),cin.tie(0),0);
#define rep(i,a,b) for(int i=(a);i<=(b);i++)

const int MAXN = 1e6+5;
inline int idx(char c){return c-'a';}
struct Node{
    int son[26],flag,fail;
}trie[MAXN*10];
int n,cntt;
string s;
queue<int> q;
void insert(string &s){//字典树插入模式串
    int siz = s.size(),v,u = 1;//根节点开始
    rep(i,0,siz-1){
        v = idx(s[i]);
        if(!trie[u].son[v]) trie[u].son[v] = ++cntt;
        u = trie[u].son[v];
    }
    trie[u].flag++;//记数
}
void getfail(){//处理失配指针
    rep(i,0,25) trie[0].son[i] = 1;//虚节点0
    q.push(1);trie[1].fail = 0;
    //建一个虚节点0号节点,将1的所有儿子指向1,然后1的fail指向0就OK了
    int u,v,ufail;
    while(!q.empty()){
        u = q.front();q.pop();
        rep(i,0,25){
            v = trie[u].son[i];
            ufail = trie[u].fail;
            if(!v){trie[u].son[i]=trie[ufail].son[i];continue;}
            //如果这个分支不满足,则会和失配的情况类似去跳转
            trie[v].fail = trie[ufail].son[i];
            //((他父亲节点)的失配指针指向的节点)的(和这个节点字母相同的儿子)
            q.push(v);
        }
    }
}
int query(string &s){//匹配
    int siz = s.size(),u = 1,v,k,ans = 0;
    rep(i,0,siz-1){
        v = idx(s[i]);
        k = trie[u].son[v];//k用来跳fail
        while(k&&trie[k].flag!=-1){//找到了没标记的单词
            ans += trie[k].flag;trie[k].flag = -1;//计数,并标记走过
            k = trie[k].fail;//跳fail,如果一个串匹配成功,那它的fail一定也能匹配
        }
        u = trie[u].son[v];
    }
    return ans;
}
int main(){
    cntt = 1;//初始化cnt
    cin>>n;
    string hc;
    rep(i,1,n){
        cin>>s;insert(s);
    }
    getfail();
    cin>>s;
    cout<<query(s)<<endl;
}

哈希
哈希表 拉链法

const int med = 1000007;//模数 
const int MAXN = 2e6+5;//数据总量 

struct Hash_table{
	struct Node{
		int next,value,key;
	}data[MAXN];//数据的总量,从1开始 
	int head[med],size;//head记录每个哈希值的链表的第一个节点
	//size记录节点总数 
	int f(int key){ return key%med; }//求哈希值
	int find(int key){
		for(int p = head[f(key)];p;p=data[p].next)//遍历这个哈希值上的链表 
			if(data[p].key==key) return data[p].value;
		return -1; 
	}
	int update(int key,int value){//更新value 
		for(int p = head[f(key)];p;p=data[p].next)
			if(data[p].key==key) return data[p].value = value;
		return -1;
	}
	int add(int key,int value){
		if(find(key)!=-1) return -1;//这个值已经被插入了
		data[++size] = (Node){head[f(key)],value,key};//从链表头部插入 
		head[f(key)] = size;//标记该链表的第一个节点 
		return value; 
	}
};

其他算法
莫队
洛谷P2709 小B的询问
https://www.luogu.com.cn/problem/P2709

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define ll long long
const int MAXN = 5e4+5;
int cnt[MAXN];//记录数字在区间[l,r]内出现的次数
int pos[MAXN],a[MAXN];
ll ans[MAXN];
int n,m,k,res;
struct Q{
    int l,r,k;//k记录原来的编号
    friend bool operator < (Q x,Q y){//同一个分块内r小的排前面;不同分块则按分块靠前的
        return pos[x.l]==pos[y.l]?x.r<y.r:pos[x.l]<pos[y.l];
    }
}q[MAXN];

void Add(int pos){
    res -= cnt[a[pos]]*cnt[a[pos]];
    cnt[a[pos]]++;
    res += cnt[a[pos]]*cnt[a[pos]];
}
void Sub(int pos){
    res -= cnt[a[pos]]*cnt[a[pos]];
    cnt[a[pos]]--;
    res += cnt[a[pos]]*cnt[a[pos]];
}
int main(){
    cin>>n>>m>>k;//k为数字范围
    memset(cnt,0,sizeof(cnt));
    int siz = sqrt(k);//每个分块的大小
    rep(i,1,n){
        cin>>a[i];
        pos[i] = i/siz;//分块
    }
    rep(i,1,m){
        cin>>q[i].l>>q[i].r;
        q[i].k = i;//记录原来的编号,用于打乱顺序后的还原
    }
    sort(q+1,q+1+m);
    res = 0;//初始化res
    int l = 1,r = 0;//当前知道的区间
    //因为是闭区间,如果是[1,1]的话则一开始就包含一个元素了
    rep(i,1,m){//莫队的核心,注意加减的顺序
        while(q[i].l<l) Add(--l);
        while(q[i].l>l) Sub(l++);
        while(q[i].r<r) Sub(r--);
        while(q[i].r>r) Add(++r);
        ans[q[i].k] = res;
    }
    rep(i,1,m) cout<<ans[i]<<endl;
}

其他算法
扫描线
洛谷P5490 【模板】扫描线
https://www.luogu.com.cn/problem/P5490

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;

#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define ll long long 
#define ls (x<<1)
#define rs (x<<1|1)//这种方法感觉还挺好的

int cansel_sync=(ios::sync_with_stdio(0),cin.tie(0),0);
const int MAXN = 2e5+5;//这里要开n的两倍
//线结构体
struct Line{
    ll l,r,h;
    int qz;//记录位置和权值
    bool operator < (Line &rhs){
        return h < rhs.h;
    }
}line[MAXN];
int n;
ll x1,y1,x2,y2;
ll X[MAXN];
//线段树
struct Segt{
    int l,r;//是X的下标,即离散化后的
    int sum;//sum是被完全覆盖的次数
    ll len;//len是区间内被盖住的长度
    //因为每次查询都是查询根节点,所以这边不需要懒惰标记
}t[MAXN<<3];//一个边有两个点,所以这里要开8倍
void build(int x,int l,int r){
    t[x].l = l;t[x].r = r;
    t[x].len = t[x].sum = 0;
    if(l==r) return;//到了叶子节点
    int mid = (l+r)>>1;
    build(ls,l,mid);
    build(rs,mid+1,r);
}
void push_up(int x){
    int l = t[x].l,r = t[x].r;
    if(t[x].sum) t[x].len = X[r+1]-X[l];//x的区间是X[l]到X[r+1]-1
    else t[x].len = t[ls].len + t[rs].len;//合并儿子的信息
}
void update(int x,int L,int R,int v){//这里的LR存的是实际值
    //这里如果是线段L,R,线段树上是L到R-1的部分维护
    int l = t[x].l,r = t[x].r;
    if(X[r+1]<=L||R<=X[l]) return;//加等于,不然会搞到无辜的线
    if(L<=X[l]&&X[r+1]<=R){
        t[x].sum += v;//修改覆盖次数
        push_up(x);
        return;
    }
    update(ls,L,R,v);
    update(rs,L,R,v);
    push_up(x);
}
int main(){
    cin>>n;
    rep(i,1,n){
        cin>>x1>>y1>>x2>>y2;
        X[2*i-1] = x1,X[2*i] = x2;//一会儿离散化要用的,这里存实际值
        line[2*i-1] = Line{x1,x2,y1,1};//开始的线
        line[2*i] = Line{x1,x2,y2,-1};//结束的线
    }
    n<<=1;//line的数量是四边形数量的2倍
    sort(line+1,line+1+n);
    sort(X+1,X+1+n);
    int tot = unique(X+1,X+n+1)-(X+1);//去除重复相邻元素,并且tot记录总数
    build(1,1,tot-1);//为什么是tot-1?
    //因为线段树只需要维护X[1]到X[tot]-1这一段的,实际长度是向右贴的
    ll res = 0;
    rep(i,1,n-1){//每次高度是line[i+1].h-line[i].h,所以是到n-1就行
        update(1,line[i].l,line[i].r,line[i].qz);//扫描线加入线段树
        res += t[1].len*(line[i+1].h-line[i].h);
    }
    cout<<res<<endl;
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值