我的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
    评论
1 图论 3 1.1 术语 3 1.2 独立集、覆盖集、支配集之间关系 3 1.3 DFS 4 1.3.1 割顶 6 1.3.2 桥 7 1.3.3 强连通分量 7 1.4 最小点基 7 1.5 拓扑排序 7 1.6 欧拉路 8 1.7 哈密顿路(正确?) 9 1.8 Bellman-ford 9 1.9 差分约束系统(用bellman-ford解) 10 1.10 dag最短路径 10 1.11 二分图匹配 11 1.11.1 匈牙利算法 11 1.11.2 KM算法 12 1.12 网络流 15 1.12.1 最大流 15 1.12.2 上下界的网络的最大流 17 1.12.3 上下界的网络的最小流 17 1.12.4 最小费用最大流 18 1.12.5 上下界的网络的最小费用最小流 21 2 数论 21 2.1 最大公约数gcd 21 2.2 最小公倍数lcm 22 2.3 快速幂取模B^LmodP(O(logb)) 22 2.4 Fermat小定理 22 2.5 Rabin-Miller伪素数测试 22 2.6 Pollard-rho 22 2.7 扩展欧几里德算法extended-gcd 24 2.8 欧拉定理 24 2.9 线性同余方程ax≡b(mod n) 24 2.10 中国剩余定理 25 2.11 Discrete Logging(BL == N (mod P)) 26 2.12 N!最后一个不为0的数字 27 2.13 2^14以内的素数 27 3 数据结构 31 3.1 堆(最小堆) 31 3.1.1 删除最小值元素: 31 3.1.2 插入元素和向上调整: 32 3.1.3 堆的建立 32 3.2 并查集 32 3.3 树状数组 33 3.3.1 LOWBIT 33 3.3.2 修改a[p] 33 3.3.3 前缀和A[1]+…+A[p] 34 3.3.4 一个二维树状数组的程序 34 3.4 线段树 35 3.5 字符串 38 3.5.1 字符串哈希 38 3.5.2 KMP算法 40 4 计算几何 41 4.1 直线交点 41 4.2 判断线段相交 41 4.3 三点外接圆圆心 42 4.4 判断点在多边形内 43 4.5 两圆交面积 43 4.6 最小包围圆 44 4.7 经纬度坐标 46 4.8 凸包 46 5 Problem 48 5.1 RMQ-LCA 48 5.1.1 Range Minimum Query(RMQ) 49 5.1.2 Lowest Common Ancestor (LCA) 53 5.1.3 Reduction from LCA to RMQ 56 5.1.4 From RMQ to LCA 57 5.1.5 An algorithm for the restricted RMQ 60 5.1.6 An AC programme 61 5.2 最长公共子序列LCS 64 5.3 最长上升子序列/最长不下降子序列(LIS) 65 5.3.1 O(n^2) 65 5.3.2 O(nlogn) 66 5.4 Joseph问题 67 5.5 0/1背包问题 68 6 组合数学相关 69 6.1 The Number of the Same BST 69 6.2 排列生成 71 6.3 逆序 72 6.3.1 归并排序求逆序 72 7 数值分析 72 7.1 二分法 72 7.2 迭代法(x=f(x)) 73 7.3 牛顿迭代 74 7.4 数值积分 74 7.5 高斯消元 75 8 其它 77

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值