个人ACM模板总结

大二下开学接触ACM(准确来说是2020的寒假,由于疫情只能在家,就接触了ACM),逃了一年课,终于在2020ICPC济南站(162名)拿了一个铜(大三上),也算在退役前拿了个奖(由于大一结束当了两年兵,导致今年就是icpc大限的第五年,这场也是我可以打的最后一场了,上一场南京站216名差6名遗憾打铁),开心特将自己的模板总结了一下,也看看着自己这一年来学了什么。

个人ACM模板总结——AC_Jobim

一、图论

(一)链式前向星

1.对于存在点权的,可以创建虚拟原点。
struct edge{
	int ne,v,w;
}e[N];
int h[N],idx;
//存边
void add(int a,int b,int w){
	e[idx].v = b;
	e[idx].ne = h[a];
	e[idx].w = w;
	h[a] = idx++;
}
注意初始化:memset(h,-1,sizeof(h));

(二)最短路

1.Dijkstra算法:O(mlogn)

void dij(int x){
    memset(d,0x3f,sizeof d);
    d[x] = 0;
    int i;
    priority_queue<PII,vector<PII>,greater<PII> > q;
    q.push({d[x],x});
    while(!q.empty()){
        PII t = q.top();q.pop();
//取出队列中当前距离最小的点
        int x = t.second;
        if(vis[x])  continue;
        vis[x] = 1;
        for(i = h[x];i!=-1;i = e[i].ne){
            int v = e[i].v;
            int w = e[i].w;
//通过该点来更新距离
            if(d[v] > d[x]+w){
                d[v] = d[x] + w;
                q.push({d[v],v});
            }
        }
    }
}

2.bellman_ford:O(nm)

struct Node{
    int a,b,w;
}e[N];//存图方式
//求从x号点到n号点最多经过k条边的最短距离
void bellman_ford(int x,int k){
    int i,j;
    memset(d,0x3f,sizeof d);
    d[x] = 0;
    for(i = 1;i<=k;i++){
        //利用上一次更新的距离求 
        memcpy(lastd,d,sizeof d);
        for(j = 1;j<=m;j++){
            int a = e[j].a;
            int b = e[j].b;
            int w = e[j].w;
        //    cout<<a<<" "<<b<<" "<<w<<endl;
            if(lastd[a]+w<d[b]){
                d[b] = lastd[a] + w;
            }
        }
    }
    if(d[n] > inf/2){//由于存在负边
        cout<<"impossible"<<endl;
    }else{
        cout<<d[n]<<endl;
    }
}

3.Floyed算法:O(n^3)

//可以处理负权,但不能有负权回路
int d[N][N];//d[i][j]表示从点i到点j的最短路
int n,m;
void floyd(){
    int i,j,k;
    for(k = 1;k<=n;k++)
		for(i = 1;i<=n;i++)
			for(j = 1;j<=n;j++)
				d[i][j] = min(d[i][j],d[i][k]+d[k][j]);
}
int main(){
    int i,j,k; cin>>n>>m>>k;
    memset(d,0x3f,sizeof d);
    for(i = 1;i<=n;i++) d[i][i] = 0;
    for(i = 1;i<=m;i++){
        int a,b,c; cin>>a>>b>>c;
        d[a][b] = min(d[a][b],c);//防止有重边
    }
    floyd();
    for(i = 1;i<=k;i++){
        int a,b;
        cin>>a>>b;
        if(d[a][b] > inf/2){
            cout<<"impossible"<<endl;
        }else
            cout<<d[a][b]<<endl;
    }
    return 0;
}
 

4.Spfa求最短路:边权可能是负值,但不存在负权回路

  1. 队首x出队
  2. 遍历所有以队首为起点的有向边(x,i),
    若d[x]+w(x,i)<d[i],则更新d[i]
  3. 如果点i不在队列中,则i入队
  4. 若队列为空,跳出循环;否则执行1
void spfa(int x){
    int i,j;
    memset(d,0x3f,sizeof d);
    d[x] = 0;
    queue<int> q;
    q.push(x);vis[x] = 1;//在队列中标记为1
    while(!q.empty()){
        int x = q.front();
        q.pop();
        vis[x] = 0;//出队列标记为0
        for(i = h[x];i!=-1;i = e[i].ne){
            int v = e[i].v;
            int w = e[i].w;
            if(d[x] + w < d[v]){
                d[v] = d[x]+w;
                if(vis[v] == 0){
                    //如果不在队列中加入队列
                    q.push(v);
					vis[v] = 1;
                }
            }
        }
    }
}

5.Spfa判断负环:O(m),最坏O(nm)

1.跟负环有关的题最好加虚拟原点,而且不要忘了多开数组
2.spfa判负环的玄学优化:①将队列改成栈②虚拟原点逆顺序建边(针对数据)③提前结束判断条件,比如:更新的次数超过一半点数就算有负环。
//cnt[]记录当前更新的次数
int cnt[N],d[N],vis[N];
//存在负环返回true
bool spfa(){
    int i,j;
    queue<int> q;
    //构建一个顶点到其余各顶点的距离为0 
    for(i = 1;i<=n;i++)
        add(0,i,0);
    memset(d,0x3f,sizeof d);memset(vis,0,sizeof vis);
    memset(cnt,0,sizeof cnt);
    d[0] = 0;
    q.push(0);vis[0] = 1;
    while(q.size()){
        int x = q.front();    q.pop();
        vis[x] = 0;//出队列标记为0
        for(i = h[x];i!=-1;i = e[i].ne){
            int v = e[i].v;
            int w = e[i].w;
            if(d[x] + w < d[v]){
                d[v] = d[x]+w; cnt[v] ++ ;
                //如果更新次数超过n次则存在负环
				//
                if(cnt[v]>n+1)    return true;
                if(vis[v] == 0){
                    //如果不在队列中加入队列
                    q.push(v);
                    vis[v] = 1;
                }
            }
        }
    }
    return false;
}
 

6.Dijkstra算法:朴素版

int dij(int x){
    int i,j;
    memset(d,0x3f,sizeof d);
    d[x] = 0;
    for(j = 1;j<=n;j++){
        int t = 0;
        for(i = 1;i<=n;i++)
            //找到当前距离最小的点
            if(d[i]<d[t] && vis[i] == 0)
                t = i;
        vis[t] = 1;
        for(i = 1;i<=n;i++)
            if(g[t][i] + d[t] < d[i])
                d[i] = g[t][i] + d[t];
    }
    return d[n];
} 

(三)最小生成树

1.Prim算法:O(n^2),用于稠密图,邻接矩阵

bool prim(int x){
    int i,j;
    //初始化距离
    memset(d,0x3f,sizeof d);
    for(i = 1;i<=n;i++)
        d[i] = min(d[i],g[x][i]);//防止重边
    d[x] = 0;vis[x] = 1;//加入集合
    for(i = 1;i<n;i++){
        //执行n-1次循环
        //选出当前最小的边
        int t = -1;
        for(j = 1;j<=n;j++)
            if((t == -1 || d[j]<d[t] ) && vis[j] == 0)
                t = j;
        if(d[t] == inf) return false;
        vis[t] = 1;//加入集合
        ans+=d[t];//求最小生成树
        //更新操作
        for(j = 1;j<=n;j++)
            d[j] = min(d[j],g[t][j]);
    }
    return true;
}

2.Prim算法:O(n^2),邻接表

bool prim(int x){
	int i,j; 
	memset(d,0x3f,sizeof(d));
	for(i = h[x];i!=-1;i = e[i].ne){
		int v = e[i].v,w = e[i].w;
		d[v] = min(d[v],w);//防止有重边的存在 
	}
	vis[x] = 1;//将x顶点加入集合 
	//进行n-1次循环,找到其余的n-1个点 
	for(i = 1;i<N;i++){
		//找到当前里集合最近的边
		int minv = -1,minw = inf;
		for(j = 1;j<=N;j++)
			if(vis[j] == 0 && d[j]<minw){
				minw = d[j];	minv = j;
			}
		//如果没有距离最小的边,则返回false 
		if(minv==-1)	return false;
		vis[minv] = 1;//加入集合 	ans += minw;
		//进行距离的更新
		for(j  = h[minv];j!=-1;j = e[j].ne){
			int v = e[j].v,w = e[j].w;
			d[v] = min(w,d[v]);
		}
	}
	return true; 
}

3.Kruskal算法:O(mlogm)

存储方式:
struct edge{
	int a,b,w;
}e[NN];
bool cmp(edge a,edge b){
	return a.w<b.w;
}
bool kruskal(){
	int i,num =1;
	//初始化并查集 
	for(i =1;i<=N;i++)
		f[i] = i;
	//对边进行排序 
	sort(e+1,e+M+1,cmp);
	for(i = 1;i<=M;i++){
		int a = e[i].a,b =e[i].b;
		if(find(a)!=find(b)){
			unio(a,b);
			//得到最小生成树的总权值 
			ans+=e[i].w;
			num++;
		}
		//如果找到N个点,则退出程序 
		if(num == N)
			return true;
	}
	return false;
} 

(四)二分图

1.匈牙利算法:O(nm)

int main(){
	for(i = 1;i<=n1;i++){
		memset(vis,0,sizeof vis);
		//如果匹配成功,则匹配总数+1 
		if(find(i))ans++;
	}
	printf("%d",ans);
	return 0;
}

//对于第x个,能够匹配返回true,不能匹配返回false 
bool find(int x){
	for(int i = h[x];i!=-1;i = e[i].ne){
		int v = e[i].v;
		//如果曾经查找过第v个点,但是没有成功 
		if(vis[v] == 0){
			vis[v] = 1;
			//第v个没有匹配或者第v个的匹配对象可以匹配成功 
			if(match[v] == 0 || find(match[v])){
				match[v] = x;	return true;
			}
		}
	}
	return false;
} 

2.KM()算法,求二分图的带权最大匹配,O(n^3)

using namespace std;
typedef long long ll;
typedef pair<ll,ll> PII;
const int N = 605;
const int inf = 0x3f3f3f3f;
PII a[N];
ll n,b[N],c[N];
ll w[N][N], lx[N],ly[N];
ll linker[N], slack[N];
bool visy[N];
ll pre[N];
void bfs(ll k){
	int i,j;
    ll x,y = 0,yy = 0,delta;
    memset(pre,0,sizeof(pre));
    for(i =1;i<=n;i++) slack[i] = inf;
    linker[y] = k;
    while(1){
        x = linker[y];delta = inf;visy[y] = true;
        for(i=1;i<=n;i++){
            if(!visy[i]){
                if(slack[i] > lx[x] + ly[i] - w[x][i] ){
                    slack[i] = lx[x] + ly[i] - w[x][i];
                    pre[i] = y;
                }
                if(slack[i] < delta) delta = slack[i], yy = i;
            }
        }
        for(i = 0;i <= n;i++){
            if(visy[i]) lx[linker[i]] -= delta , ly[i] += delta;
            else slack[i] -= delta;
        }
        y = yy ;
        if(linker[y] == -1) break;
    }
    while(y) linker[y] = linker[pre[y]], y = pre[y];
}
ll KM(){
	int i,j;
    memset(lx,0,sizeof(lx));
    memset(ly,0,sizeof(ly));
    memset(linker,-1,sizeof(linker));
    for(i = 1;i<=n;i++){
        memset(visy,false, sizeof(visy));
        bfs(i);
    }
    ll res = 0 ;
    for(i = 1;i<=n;i++){
        if(linker[i]!=-1){
            res += w[linker[i]][i];
        }
    }
    return res;
}
int main(){
	int i,j;
	while(cin>>n){
		for(i = 1;i<=n;i++){
			for(j = 1;j<=n;j++){
				scanf("%d",&w[i][j]);
			}
		}
		cout<<KM()<<endl;
	}
	return 0;
}

(五)差分约束

解题步骤:

  1. 先将每个不等式xi<=xj+ck,转化成一条从点j到点i,长度为ck的一条边。
  2. 找一个超级源点,使得该源点一定可以遍历到所有边。
  3. 从源点求一遍单源最短路
    结果一:如果存在负环,则原不等式组一定无解
    结果二:如果没有负环,则d[i]就是原不等式组的一个可行解
    对于解得最大值和最小值如何求解:
  • 当求解得最大值得时候,则应该将所有条件转化为:xi<=xj+ck,然后求最短路就是解得最大值。
  • 当求解得最小值得时候,则应该将所有条件转化为:xi>=xj+ck,然后求最长路就是解得最小值。

(六)Lca算法

1.树上倍增法O(nlogn)

const int N = 40005;
const int M = 2*N;
int f[N][20];//f[i][k]表示节点i向上走2^k步的节点
//注意数组一定要比depth多开一点 
int n,m;
int root; 
struct edge{
    int v,w,ne;
}e[M];
int h[N],idx,vis[N];
int d[N];//每个节点的深度
int depth = (int)log(N)/log(2) + 1;
void add(int a,int b){
    e[idx].v = b;   e[idx].ne = h[a];   h[a] = idx++;
}
//如果从i开始跳2^k步会跳出根节点,则f[i][k] = 0,d[0] = 0;
//预处理每个f[i][j]和d[i];
void bfs(int root){
    int i,j;
    queue<int> q;q.push(root);
    vis[root] = 1; d[root] = 1;//根节点深度为1
    while(!q.empty()){
        int x = q.front();q.pop();
        for(i = h[x];i!=-1;i=e[i].ne){
            int v = e[i].v;
            if(vis[v])  continue;
            vis[v] = 1;q.push(v);
            d[v] = d[x] + 1;f[v][0] = x;//向上走一步
            //更新数组f
            for(j = 1;j<=depth;j++)
                f[v][j] = f[f[v][j-1]][j-1];
        }
    }
}
//返回x和y的最近公共祖先
int lca(int x,int y){
    int i,j;
    //a的深度>b的深度
    if(d[y]>d[x])   swap(x,y);
    //先将两个节点调整到同一深度
    for(i = depth;i>=0;i--){
        //如果跳出的情况,d[0]=0肯定不成立
        if(d[f[x][i]] >= d[y])  x = f[x][i];
    }
    //如果相等直接跳出
    if(x == y)  return x;
    //将x和y一起跳
    for(i = depth;i>=0;i--){
        if(f[x][i] != f[y][i]){
            x = f[x][i];
            y = f[y][i];
        }
    }
    //则此时x,y只差一步就汇合了
    return f[x][0];
}
int main(){
    int i,j;
    memset(h,-1,sizeof h);
    cin>>n;
    for(i = 1;i<=n;i++){
        int a,b;
        cin>>a>>b;
        if(b == -1) root = a;
        else add(a,b),add(b,a);
    }
    //bfs()一遍,进行预处理f[][],求depth[];
    bfs(root);
    cin>>m;
    for(i = 1;i<=m;i++){
        int a,b;
        cin>>a>>b;
        cout<<lca(a,b);
    }
    return 0;
}

2.LCA的Tarjan算法0(n+m)

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
const int N = 10005;
const int M = 40005;
int n,m;
struct Node{
    int v,ne,w;
}e[M];
int h[N],idx,d[N],vis[N],f[N];
//记录点的三种状态
//访问完毕并且已经回溯的标记2,访问完为回溯的标记为1
int st[N];
int dis[M],lca[M];//dis[i]表示第i次查询两点之间的最短距离
//lca[i]表示第i次查询两点的最近公共祖先
vector<PII> que[N];

void add(int a,int b,int c){
    e[idx].v = b;
    e[idx].ne = h[a];
    e[idx].w =c ;
    h[a] = idx++;
}
int find(int x){
    if(f[x] != x)   f[x] = find(f[x]);
    return f[x];
}
void merge(int x,int y){
    x = find(x);
    y = find(y);
    f[y] = x;
}
void tarjan(int x){
    int i,j;
    st[x] = 1;
    for(i = h[x];i!=-1;i = e[i].ne){
        int v = e[i].v;
        if(st[v]!=0)    continue;
        d[v] = d[x] + e[i].w;
        tarjan(v);
        //v回溯后将v合并到x中
        merge(x,v);
    }
    //处理每个与x相关的查询
    for(i = 0;i<que[x].size();i++){
        int y = que[x][i].first;
        int id = que[x][i].second;
        //如果y已经回溯结束
        if(st[y] == 2){
            //记录第i个查询的最近公共祖先
            lca[id] = find(y);
            //记录第i个查询两点之间的最近距离
            dis[id] = d[x]+d[y]-2*d[lca[id]];
        }
    }
    //开始回溯将状态标记为2
    st[x] = 2;
}
int main(){
    int i,j;
    cin>>n>>m;//点数和询问
    memset(h,-1,sizeof h);
    for(i = 1;i<=n;i++) f[i] = i;
    for(i = 1;i<n;i++){
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
        add(b,a,c);
    }
    for(i = 1;i<=m;i++){
        int a,b;
        cin>>a>>b;
        que[a].push_back({b,i});
        que[b].push_back({a,i});
    }
    tarjan(1);
    for(i = 1;i<=m;i++){
        cout<<lca[i]<<" "<<dis[i]<<endl;
    }
    return 0;
}

(七)连通性

1.有向图的强连通分量(SCC):Tarjan算法O(n+m)

int stamp;//时间戳
int scc_cnt;//强连通分量的个数
int dfn[N],low[N];
//dfn[i]表示点i的时间戳,low[i]表示点i所在的强连通分量中时间戳的最小值
int stk[N],in_s[N];//stk[i]表示栈,in_s[N]表示是否在栈中
int top;
int id[N];//点i所位于的强连通分量的编号
int siz[N];//size[i]强连通分量编号为i的点的个数
void add(int a,int b){
    e[idx].v = b;
    e[idx].ne = h[a];
    h[a] = idx++;
}
void add2(int a,int b){
    e2[idx2].v = b;
    e2[idx2].ne = h2[a];
    h2[a] = idx2++;
}
void tarjan(int u){
    int i,j;
    dfn[u] = low[u] = ++stamp;
    stk[++top] = u;
    in_s[u] = 1;
    for(i = h[u];i!=-1;i = e[i].ne){
        int v = e[i].v;
        //如果之前没有访问过
        if(dfn[v] == 0){
            tarjan(v);
            low[u] = min(low[u],low[v]);
        }else if(in_s[v]){
            //如果点v在栈中
            low[u] = min(low[u],dfn[v]);
        }
    }
    if(dfn[u] == low[u]){
        ++ scc_cnt;//强连通分量的个数加1
        int y;
        do{
            y = stk[top--];//出栈
            in_s[y] = 0;
            id[y] = scc_cnt;
            siz[scc_cnt]++;
        }while(y != u);
    }
}
int main(){
    int i,j;
    cin>>n>>m;
    memset(h,-1,sizeof h);
    for(i = 1;i<=m;i++){
        int a,b;
        cin>>a>>b;
        add(a,b);
    }
    for(i = 1;i<=n;i++){
        if(dfn[i] == 0){
            tarjan(i);
        }
    }
    unordered_set<ll> set;
    //进行缩点,缩点的过程要给边判重
    for(i = 1;i<=n;i++){
        for(j = h[i];j !=-1;j = e[j].ne){
            int v = e[j].v;
            if(id[i] != id[v]){
                ll hash = (ll)id[v]*100000+id[i];
                if(set.find(hash) != set.end())   continue;
                set.insert(hash);
                add2(id[i],id[v]);
            }
        }
    }
    return 0;
}

2.无向图的边双连通分量:E-DCC

int dfn[N],low[N],timestamp;
bool bridge[N];//如果是桥,边的下标标记为true
int id[N],dcc_cnt;
int stk[N],top;//栈
//求E-DCC边双连通分量
//last为父边
void tarjan(int x,int last){
    int i,j;
    dfn[x] = low[x] = ++timestamp;
    stk[++top] = x;
    for(i = h[x];i!=-1;i = e[i].ne){
        int v = e[i].v;
        if(dfn[v] == 0){
            tarjan(v,i);
            low[x] = min(low[x],low[v]);
            if(dfn[x] < low[v]) //判断这条边是否是桥
                bridge[i] = bridge[i ^ 1] = true;
        }else if(i != (last^1)){ //如果不是反向边
            low[x] = min(low[x],dfn[v]);
        }
    }
    if(dfn[x] == low[x]){
        dcc_cnt++; int v;
        do{
            v = stk[top--];
            id[v] = dcc_cnt;
        }while(v != x);
    }
}
int main(){
    int i,j; cin>>n>>m; memset(h,-1,sizeof h);
    for(i = 1;i<=m;i++){
        int a,b; cin>>a>>b; add(a,b);    add(b,a);
    }
    //对于连通图不用循环遍历
    tarjan(1,-1);
    //缩点重新建边
    for(i = 1;i<=n;i++){
        for(j = h[i];j!=-1;j=e[j].ne){
            int v = e[j].v;
            if(bridge[j]) //如果边j是桥
                add1(id[i],id[v]); //建边
        }
    }
    return 0;
}

3.求删除割点之后连通块的数目

int dfn[N],timestamp,low[N];
int cut[N];//记录是否是割点
int ans= 0;int root;
void tarjan(int x){
    int i,j; int cnt = 0;//记录x下有几个连通块
    low[x] = dfn[x] = ++timestamp;
    for(i = h[x];i!=-1;i = e[i].ne){
        int v = e[i].v;
        if(dfn[v] == 0){
            tarjan(v);
            low[x] = min(low[x],low[v]);
            if(low[v] >= dfn[x]){ //割点判断条件
                cnt++;
      //1.根节点是割点,且子节点数大于等于2; 
      //2.非根节点是割点,且子节点中没有返祖边;
                if(x != root || cnt>1)  cut[x] = true;
            }
        }else		low[x] = min(low[x],dfn[v]);
    }
  // 如果u不是根节点 再把u的父节点的连通块加上
    if(x != root)    cnt++;
    ans = max(cnt,ans);//取最大值
}
int main(){
    int i,j;
    while(cin>>n>>m,n!=0 || m!=0){
        memset(h,-1,sizeof h);
        idx=0;timestamp = 0;ans = 0;
        for(i = 1;i<=m;i++){
            int a,b;cin>>a>>b; add(a,b);    add(b,a);
   	     }
        memset(dfn,0,sizeof dfn);
        int res = 0;
        for(root = 1;root<=n;root++){
            if(dfn[root] == 0){
                res++; tarjan(root);
            }
        }
        //求删除图中的一个点使得图中的连通块数目最大,并求出这个最大值
        cout<<res+ans-1<<endl;
    }
    return 0;
}

4.无向图的点双连通分量:V-DCC

int low[N],dfn[N],timestamp;
int stk[N],top;int dcc_cnt,root;
bool cut[N];//记录是不是割点
vector<int> dcc[N];//dcc[i]存放编号为i的v-DCC中的所有节点
void tarjan(int x){
    int i,j;
    dfn[x] = low[x] = ++timestamp; stk[++top] = x;
    //孤立点
    if(x == root && h[x] == -1){
        dcc[++dcc_cnt].push_back(x);
        return;
    }
    //满足条件的子节点的数量
    int cnt = 0;
    for(i = h[x];i!=-1;i = e[i].ne){
        int v = e[i].v;
        if(dfn[v] == 0){
            tarjan(v);
            low[x] = min(low[x],low[v]);
            if(low[v] >= dfn[x]){
                cnt++;
                if(x != root || cnt > 1)    cut[x] = true;
                dcc_cnt++;//双连通分量个数+1
                int y;
                do{
                    y = stk[top--];
                    dcc[dcc_cnt].push_back(y);
                }while(y!=v);//注意y!=v
                //割点也要放进去
                dcc[dcc_cnt].push_back(x);
            }
        }else
            low[x] = min(low[x],dfn[v]);
    }
}
int main(){
    int i,j;
    while(cin>>n>>m){
        for(i = 1;i<=dcc_cnt;i++)   dcc[i].clear();
        memset(h,-1,sizeof h);
        memset(dfn,0,sizeof dfn);
        memset(cut,false,sizeof cut);
        idx = top = dcc_cnt = timestamp =  0;
//进行建边省略
        for(root = 1;root<=n;root++){
            if(dfn[root] == 0)
                tarjan(root);
        }
  //进行缩点建边,把每一个dcc和割点都看做一个点
        for(i = 1;i<=dcc_cnt;i++){
//cnt代表割点的数量 有多少个割点那么这dcc就有多少度
            int cnt = 0;
            for(j = 0;j<dcc[i].size();j++)
                if(cut[dcc[i][j]])  cnt++;
        }
    }    
    return 0;
} 

(八)网络流

1.最大流EK()算法

int n,m,s,t;
struct Node{
    int v,ne,f;//f表示流量
}e[N];
int h[N],idx,vis[N],d[N];//d[i]表示
int pre[N];//pre[i]记录点i是从哪一条边转移过来的

//下标为i的边的反向边为i^1
void add(int a,int b,int c){
    //正向边的容量为c,反向边的容量为0
    e[idx].v = b,e[idx].f = c,e[idx].ne = h[a],h[a] = idx++;
    e[idx].v = a,e[idx].f = 0,e[idx].ne = h[b],h[b] = idx++;
}

//寻找增广路径
bool bfs(){
    int i,j;
    memset(vis,0,sizeof vis);
    queue<int> q;
    q.push(s);
    d[s] = inf;vis[s] = 1;
    while(!q.empty()){
        int x = q.front();q.pop();
        for(i = h[x];i != -1;i = e[i].ne){
            int v = e[i].v;
            //如果该点没有访问过,并且该边的容量大于0
            if(vis[v] == 0 && e[i].f>0){
                vis[v] = 1;
                //记录到达点v的最小边权
                d[v] = min(e[i].f,d[x]);
                //记录从哪条边过来的
                pre[v] = i;
                q.push(v);
                if(v == t)  return true;
            }
        }
    }
    return false;
}

//EK算法
int EK(){
    int i,j;
    int ans = 0;
    while(bfs()){
        //当存在增广路径的时候
        //正向边-=d[t],反向边+=d[t];
        ans+=d[t];
        for(i=t;i!=s;i=e[pre[i]^1].v){
            e[pre[i]].f -= d[t];
            e[pre[i]^1].f += d[t];
        }
    }
    return ans;
}

int main(){
    int i,j;
    memset(h,-1,sizeof h);
    cin>>n>>m>>s>>t;
    for(i = 1;i<=m;i++){
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
    }
    //求最大流
    cout<<EK()<<endl;
    return 0;
}

2.最大流Dinic()

int n,m,s,t;
struct Node{
    int v,ne,f;
}e[N];
int h[N],idx;
int dis[N],vis[N];
//dis[i]表示点i到源点的距离,用于记录分层图信息
int cur[N];
// cur[a]表示节点a在dfs搜索中第一次开始搜索的边的下标,也称当前弧,用于优化dfs速度
//构建残留网络,正向边为源网络的容量-流量,反向边为原网络的流量
void add(int a,int b,int c){
    e[idx].v = b,e[idx].ne = h[a],e[idx].f = c,h[a] = idx++;
    e[idx].v = a,e[idx].ne = h[b],e[idx].f = 0,h[b] = idx++;
}
//构建分层图
bool bfs(){
    int i,j;
    //每个节点的层数初始化为-1
    memset(dis,-1,sizeof dis);
    queue<int> q;
    cur[s] = h[s];
    q.push(s);dis[s] = 0;//初始节点的深度为0
    while(!q.empty()){
        int x = q.front();q.pop();
        for(i = h[x];i!=-1;i = e[i].ne){
            int v = e[i].v;
            if(dis[v] == -1 && e[i].f>0){
                dis[v] = dis[x] + 1;
                //记录当前弧
                cur[v] = h[v];
                if(v == t)  return true;
                q.push(v);
            }
        }
    }    
    return false;
}
//当从原点流向x的最大流量为limit的时候,到达汇点的最大增量
int dfs(int x,int limit){
    int i,j;
    if(x == t)  return limit;//如果当前是汇,
    int flow = 0;//记录改点允许流过的流量
    for(i = cur[x];i!=-1 && flow<limit;i = e[i].ne){
        int v = e[i].v;
        cur[x] = i;//当前弧优化
        if(dis[v] == dis[x]+1 && e[i].f>0){
            int t = dfs(v,min(limit-flow,e[i].f));
            //如果从点v出发,允许流过的流量为0,则删除该点
            if(t == 0)  dis[v] = -1;
            flow += t;
            //处理边
            e[i].f -= t,e[i^1].f += t;
        }
    }
    return flow;
}
int dinic(){
    int ans = 0;
    //bfs()返回有没有增广路,如果有增广路则建立出分层图。
    while(bfs()){
        int res;
        while(res = dfs(s,inf))//搜索当前分层图中的增广路
            ans+=res;
    }
    return ans;
}

int main(){
    int i,j;
    cin>>n>>m>>s>>t;
    memset(h,-1,sizeof h);
    for(i = 1;i<=m;i++){
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
    }
    cout<<dinic()<<endl;
    return 0;
}

3.最小费用流EK()

int n,m,s,t;//s表示源点,t表示汇点
struct Node{
    int v,ne,f,w;
}e[N];
int h[M],idx,pre[M],incf[M],cost[M],vis[M];
//pre[i]表示从点i转移过来的边
//incf[i]表示从源点是到i路径中的最小容量
void add(int a,int b,int c,int d){
    e[idx].v = b,e[idx].ne = h[a],e[idx].f = c,e[idx].w = d,h[a] = idx++;
    e[idx].v = a,e[idx].ne = h[b],e[idx].f = 0,e[idx].w = -d,h[b] = idx++;
}
bool bfs(){
    int i,j;
    memset(vis,0,sizeof vis);
    memset(incf,0,sizeof incf);
    memset(cost,0x3f,sizeof cost);
    cost[s]=0,incf[s] = inf,vis[s] = 1;
    queue<int> q;
    q.push(s);
    while(!q.empty()){
        int x = q.front();q.pop();
        vis[x] = 0;
        for(i = h[x];i!=-1;i = e[i].ne){
            int v = e[i].v;
            int w = e[i].w;
            if(cost[v] > cost[x]+w && e[i].f){
                cost[v] = cost[x] + w;
                pre[v] = i;
                incf[v] = min(e[i].f,incf[x]);
                if(vis[v] == 0){
                    vis[v] = 1;
                    q.push(v);
                }
            }
        }
    }
    return incf[t]>0;
}
int EK(){
    int i,j;
    int ans = 0;//返回最小费用
    while(bfs()){
        ans += cost[t]*incf[t];
        for(i=t;i!=s;i=e[pre[i]^1].v){
            e[pre[i]].f -= incf[t];
            e[pre[i]^1].f += incf[t];
        }
    }
    return ans;
}
int main(){
    int i,j;
    cin>>n>>m>>s>>t;
    memset(h,-1,sizeof h);
    for(i = 1;i<=m;i++){
        int a,b,c,d;
        cin>>a>>b>>c>>d;
        add(a,b,c,d);
    }
    
    cout<<EK()<<endl;
    
    return 0;
}

(九)其他

1.拓扑排序:可以用来判断是否存在环

queue<int> ans;//用于存放拓扑序
void topo(){
    int i,j;
    queue<int> q;
    for(i = 1;i<=n;i++)
        if(in[i] == 0)
            q.push(i);
    while(!q.empty()){
        int x = q.front();
        q.pop();
        ans.push(x);
        for(i = h[x];i!=-1;i = e[i].ne){
            int v = e[i].v;
            in[v]--;
            if(in[v] ==0)
                q.push(v);
        }
    }
}

2.求树的重心

int ans = inf;
//ans表示将重心删除后,剩余各个连通块中点数的最大值
//返回以u为跟的子树中点的数量
方法一:
int dfs(int u){
	vis[u] = 1;
	int size = 1,sum = 1; 
	for(int i = h[u];i!=-1;i = e[i].ne){
	    int v = e[i].v;
		if(vis[v] == 0){
			int a = dfs(e[i].v);
			size = max(a,size);
			sum +=a;
		}
	}
	size = max(size,n-sum);
	ans = min(ans,size);
	return sum;
}
//方法二:
int dfs(int x,int last){
    int i,j;
    int size = 0,sum = 1;//以该点为跟的连通块的最大值
    for(i = h[x];i!=-1;i = e[i].ne){
        int v = e[i].v;
        if(v == last)   continue;
        int t = dfs(v,x);
        size = max(size,t);
        sum+=t;
    }
    size = max(size,n-sum);
    ans = min(ans,size);
    return sum;
}

3.欧拉回路

//找出欧拉回路,并将欧拉回路的每条边存入栈中
//type==1,为无向图,type==2为有向图
void dfs(int u){
    int &i = h[u],j;
    for(;i!=-1;){
        int v = e[i].v;
        if(vis[i]){
            i = e[i].ne;
            continue;
        }
        vis[i] = 1;//标记为走过
        int t = i;
        if(type == 1){
            //偶数边为正,奇数边为负
            vis[i^1] = 1;
            t = (t+2)/2;
            if(i%2 == 1)    t = -t;
        }else{
            t = t+1;
        }
        i = e[i].ne;
        dfs(v);
        stac.push(t);
    }
}

二、数论

(一)质数

1.质数的判定

bool is_prime(int n){
	if(n < 2)	return false;
	for(int i = 2;i<=n/i;i++)
		if(n%i==0)
			return false;
	return true;
}

2.埃氏筛选

void is_primes(int n){
judge[0] = 1,judge[1] = 1; 
	for(int i = 2;i<=n;i++){
		if(judge[i]==0){
			//如果i为质数,则进行筛选 
			cnt++;
			for(int j = i+i;j<=n;j+=i)
				judge[j] = 1;//如果是合数标记为1 
		}
	}
}

3.线性筛选

int cnt,judge[N],prime[N];
//judge[i]如果i是合数标记为1
void is_primes(int n){
	judge[0] = 1,judge[1] = 1; 
	for(int i = 2;i<=n;i++){
		if(judge[i]==0)prime[++cnt] = i; 
		for(int j = 1;j<=cnt&&prime[j]<=n/i;j++){
			judge[prime[j]*i] = 1;//如果是合数标记为1 
			if(i%prime[j] == 0)break;
		}
	}
}

4.分解质因数

void divide(int n){
	for(int i = 2;i<=n/i;i++){
		if(n%i==0){
			int t = 0;
			while(n % i == 0){
				n/=i;
				t++;
			}
			//输出分解出的因数和个数 
			printf("%d %d\n",i,t);
		}
	}
	//如果n大于1,则n为质数 
	if(n>1)	printf("%d 1\n",n);
}

5.用素数分解质因数

void div(int n){
    int i,j;
    for(i = 1;i<=n/prime[i];i++){
        int t = prime[i];
        if(n%t == 0){
            int num = 0;
            while(n%t == 0){
                n/=t;
                num++;
            }
            res[fcnt++] ={t,num};
        }
    }
    if(n>1)
        res[fcnt++] = {n,1};
}

(二)约数

1.算数基本定理

N=P1a1 P2a2 P3a3…Pnan
这里P1<P2<P3…<Pn均为质数,其中指数ai是正整数

2.试除法求约数

void divisors(int n){
	vector<int> v;
	for(int i = 1;i<=n/i;i++){
		if(n%i == 0){
			v.push_back(i);
			if(i*i != n)
				v.push_back(n/i);
		}
	}
	//按照从小到大的顺序输出它的所有约数 
	sort(v.begin(),v.end());
	for(int i = 0;i<v.size();i++)
		printf("%d ",v[i]);
}

3.求约数的个数

N = (a1+1) * (a2+1) * (a3+1)…* (an+1)

4.求所有约数的和

N = (p10 + p11 +p12…+p1a1 ) * (p20 +p21 +p22 +…+p2a2 ) * … * (pn0 +pn1 +…+pnan )

5.欧几里得算法:求最大公约数

int gcd(int a,int b){
	return b?gcd(b,a%b):a;
}

6.欧拉函数

公式:φ(N) = N * (1 - 1/p1) * (1 - 1/p2) * (1 - 1/p3) * … * (1 - 1/pn)

//求1-n中与n互质的数的个数
int get_eular(int n){
	int ans = n;
	for(int i = 2;i<=n/i;i++){
		if(n%i == 0){
			while(n%i==0)
				n/=i;
			ans = ans/i*(i-1);
		}
	}
	if(n>1)
		ans = ans/n*(n-1);
	return ans;
}

7.线性筛欧拉函数

1.	如果i为质数,则φ(i) = i -1
2.	如果i%prime[j] ==0,则φ( i * prime[j] ) = φ(i) * prime[j]
3.	如果i%prime[j] != 0,则φ( i * prime[j] ) = (prime[j] - 1) * φ(i)
void get_euler(int x){
    int i,j;
    euler[1] = 1;
    for(i = 2;i<=x;i++){
        if(!judge[i]){
            prime[++cnt] = i;
            euler[i] = i-1;
        }
        
        for(j = 1;j<=cnt && prime[j]*i<=x;j++){
            judge[prime[j]*i] = true;
            if(i%prime[j] == 0){
                euler[i*prime[j]] = euler[i]*prime[j];
                break;
            }
            euler[i*prime[j]] = euler[i]*(prime[j]-1);
        }
    }
}

8.欧拉定理:若a与n互质,则:aφ(n)≡1(mod n)

9.费马小定理:an-1≡ 1 (mod n)

10.扩展欧几里得算法

a*x+b*y=gcd(a,b),求x和y:
ll exgcd(ll a,ll b,ll &x,ll &y){
    if(b == 0){
        x = 1,y = 0;
        return a;
    }
    ll d = exgcd(b,a%b,x,y);
    ll z = x;
    x = y;
    y = z-(a/b)*y;
    return d;
}

(三)高斯消元

const double eps = 1e-6;
int n;
double a[N][N];

int gauss(){
    int c, r;//c代表列col ,r代表row
    for (c = 0, r = 0; c < n; c ++ ){
        int t = r;
        // 先找到当前这一列,绝对值最大的一个数字所在的行号
        for (int i = r; i < n; i ++ )
            if (fabs(a[i][c]) > fabs(a[t][c]))
                t = i;
        if (fabs(a[t][c]) < eps) continue;
        // 如果当前这一列的最大数都是 0 ,那么所有数都是 0,就没必要去算了

        for (int i = c; i < n + 1; i ++ ) swap(a[t][i], a[r][i]);
        // 把当前第r行,换到最上面)去
        for (int i = n; i >= c; i -- ) a[r][i] /= a[r][c];
        for (int i = r + 1; i < n; i ++ )
            if (fabs(a[i][c]) > eps)
                for (int j = n; j >= c; j -- )
                // 从后往前,当前行的每个数字,都减去对应列 * 行首非0的数字
                    a[i][j] -= a[r][j] * a[i][c];
        r ++ ;//换下一行
    }
    //判断是无解还是无穷多解
    if (r < n){// 因为已经是阶梯型,所以 r ~ n-1 的值应该都为 0
        for (int i = r; i < n; i ++ )
            if (fabs(a[i][n]) > eps)//无解。
                return 2;
        return 1;// 否则, 0 = 0,就是r ~ n-1的方程都是多余方程
    }
    // 唯一解 
    for (int i = n-1;i>=0;i--)
        for (int j =i+1;j<n;j++)
            a[i][n] -= a[j][n] * a[i][j];

    return 0;
}

int main(){
    cin >> n;
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < n + 1; j ++ )
            cin >> a[i][j];
    int t = gauss();
    if (t == 0)
        for (int i = 0; i < n; i ++ ) printf("%.2lf\n", a[i][n]);
    else if (t == 1) puts("无穷多组解");
    else puts("唯一解");
    return 0;
} 

(四)求组合数

1.Lucas定理求组合数

int power(int a,int b,int mod){
    int res = 1;
    while(b){
        if(b%2 == 1)    res = (ll)res*a%mod;
        a = (ll)a*a%mod;
        b = b/2;
    }
    return res;
}
int C(int a,int b,int p){
    int i,j;
    if(b>a) return 0;
    int up = 1;
    int down = 1;
    for(i = 1,j=a;i<=b;i++,j--){
        up = (ll)up*j%p;//计算a*..*(a-b+1)
        down = (ll)down*i%p;//计算a!
    }
    return (ll)up*power(down,p-2,p)%p;
}
int Lucas(ll a,ll b,int p){
    if(a<p && b<p) return C(a,b,p);
    return (ll)C(a%p,b%p,p)*Lucas(a/p,b/p,p)%p;
}

int main(){
    int i,j;
    int T;
    cin>>T;
    while(T--){
        ll a,b;
        cin>>a>>b>>p;//Ca,b mod p
        cout<<Lucas(a,b,p)<<endl;
    }
    return 0;
}

2.预处理求组合数

#include<bits/stdc++.h>
using namespace std;
const int N = 100005;
typedef long long ll;
const int mod = 1e9+7;
int n,m;
int fac[N];//fac[i]表示i的阶乘
int infac[N];//infac[i]表示i的阶乘的逆元

int power(int a,int b,int mod){
    int res = 1;
    while(b){
        if(b%2 == 1)    res = (ll)res*a%mod;
        a = (ll)a*a%mod;
        b = b/2;
    }
    return res;
}

//求Cn,m
int C(int n,int m,int mod){
    return (ll)fac[n]*infac[m]%mod*infac[n-m]%mod;
}

int main(){
    int i,j;
    int T;
    fac[0] = 1;
    infac[0] = 1;
    for(i = 1;i<=N-1;i++){
        fac[i] = (ll)fac[i-1]*i%mod;
        infac[i] = power(fac[i],mod-2,mod);
    }
    cin>>T;
    int ans;
    for(i = 1;i<=T;i++){
        cin>>n>>m;
        cout<<C(n,m,mod)<<endl;
    }
    return 0;
}

3.不取模求组合数(高精度)

int n,m;
int num[N];
int cnt,judge[N],prime[N];
//judge[i]如果i是合数标记为1
void is_primes(int n){
	judge[0] = 1,judge[1] = 1; 
	for(int i = 2;i<=n;i++){
		if(judge[i]==0)prime[++cnt] = i; 
		for(int j = 1;j<=cnt&&prime[j]<=n/i;j++){
			judge[prime[j]*i] = 1;//如果是合数标记为1 
			if(i%prime[j] == 0)break;
		}
	}
}
//对n!进行分解质因数
void sovle(int n,int flag){
    int i,j;
    for(i = 1;i<=cnt;i++){
        int t  = prime[i];
        int cn = 0;
        int res = t;
        while(res<=n){
            cn += n/res;
            res = res*t;
        }
        num[i] += cn*flag;
    }
}
vector<int> mul(vector<int> &A,int a){
	vector<int> C;
	int i,t = 0;//t表示进位
	for(i = 0;i<A.size();i++){
		t+=A[i]*a;
		C.push_back(t%10);
		t/=10;
	}
	while(t){
		C.push_back(t%10);
		t/=10;
	}
	while(C.back()==0 && C.size()>1){
	    C.pop_back();//去掉前导0  
	}
	return C;
}
//获得n!分解质因数后p的指数
int get(int n,int p){
    int res = 0;
    ll t = p;
    while(t<=n){
        res += n/t;
        t = t*p;
    }
    return res;
}
int main(){
    int i,j;
    int a,b;
    //输出组合数Ca,b
    cin>>a>>b;
    is_primes(N-1);
    //筛一遍素数
 //   sovle(a,1);
 //   sovle(b,-1);
//    sovle(a-b,-1);
    for(i = 1;i<=cnt;i++){
        num[i] = get(a,prime[i])-get(b,prime[i])-get(a-b,prime[i]);
    }
    vector<int> A;
    A.push_back(1);
    for(i = 1;i<=cnt;i++){
        int t = prime[i];
        for(j = 1;j<=num[i];j++){
        //    cout<<t<<" ";
            A = mul(A,t);
        }
    }
    for(i = A.size()-1;i>=0;i--)
		printf("%d",A[i]);
    return 0;
}

(四)SG函数

题目大意:给定nn堆石子,两位玩家轮流操作,每次操作可以取走其中的一堆石子,然后放入两堆规模更小的石子(新堆规模可以为0,且两个新堆的石子总数可以大于取走的那堆石子数),最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜

#include<bits/stdc++.h>
using namespace std;
const int N = 105;
int f[N];
int n,a[N];
int sg(int x){
    int i,j;
    if(f[x] != -1)  return f[x];
    unordered_set<int> se;
    //每一堆可以变成不大于原来那堆的任意大小的两堆
    for(i = 0;i<x;i++){
        for(j = 0;j<x;j++){
            //SG函数理论,多个独立局面的SG值,等于这些局面SG值的异或和。
            se.insert(sg(i)^sg(j));
        }
    }
    //找到最小的不能到达的数
    for(i = 0;;i++){
        if(se.count(i) == 0)    return f[x] = i;
    }
}
int main(){
    int i,j;
    cin>>n;
    int ans = 0;
    memset(f,-1,sizeof f);
    for(i = 1;i<=n;i++){
        cin>>a[i];
        ans = ans ^ sg(a[i]);
    }
    //如果ans不为0则先手必胜
    if(ans == 0)    cout<<"No"<<endl;
    else    cout<<"Yes"<<endl;
    return 0;
} 

(五)矩阵快速幂:求斐波那契数列的第n项

//矩阵乘法
void mul(int c[2][2],int a[2][2]){
    int i,j,k;
    int temp[2][2] = {0};
    for(i = 0;i<n;i++){
        for(j = 0;j<n;j++){
            for(k = 0;k<n;k++){
                temp[i][j] = (temp[i][j]+c[i][k]*a[k][j])%mod;
            }
        }
    }
    memcpy(c,temp,sizeof temp);
}
int powm(int b){
    int i,j,k;
    int res[2][2] = {{1,0},{0,1}};//单位矩阵
    int c[2] = {0};
    int x[2] = {0,1};
    int a[2][2] = {{0,1},{1,1}};
    while(b){
        if(b%2 == 1)    mul(res,a);
        mul(a,a);
        b/=2;
    }
    //x*res
    for(j=0;j<n;j++){
        for(k = 0;k<n;k++){
            c[j] = (c[j]+x[k]*res[k][j])%mod;
        }
    }
    return c[0];
}
int main(){
    int i,j;
    while(cin>>m,m!=-1){
        n = 2;
        cout<<powm(m)<<endl;
    }
    return 0;

(六)常见公式

1.卡特兰数

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.错排公式

假设有n个元素,n个位置,每个元素都有自己唯一的正确位置,问,所有元素都处在错误位置有多少可能?
D(n)=(n-1)*( D(n-1) + D(n-2) ),易得 D(1)=0 , D(2)=1。

3.常用前n项和公式

在这里插入图片描述

三、数据结构

(一)堆

void up(int k){ //up操作
	int a = k;
	while(a!=1 && h[a/2]>h[a]){
		swap(h[a/2],h[a]);
		a = a/2;
	}
}
void push(int x){
	h[++index] = x;
	up(index);
}
void down(int k){//down操作非递归
	int a = k;
	while(a*2<=index){
		int b;
		if(2*a+1<=index)
			b = h[2*a]<h[2*a+1]?2*a:2*a+1;
		else		b = 2*a;
		if(h[a]>h[b]){
			swap(h[a],h[b]);
			a = b;
		}
		Else		break;
	}
}
void down(int k){//down递归操作
	int a=k;
	if(2*k<=index && h[k]>h[2*k])	a = 2*k;
	if(2*k+1<=index && h[a]>h[2*k+1])	a = 2*k+1; 
	if(a!=k){
		swap(h[a],h[k]);
		down(a);
	}
}
void pop(int k){ //删除下标为k的元素 
	swap(h[1],h[index]);
	index--;
	down(k);
}
int top(){//获得堆顶元素	
	return h[1];	
} 

(二)并查集

const int N = 100005;
int f[N];
int cnt[N];//维护连通块中点的数量
int find(int x){
    if(f[x] != x)   f[x] = find(f[x]);
    return f[x];
}
void merge(int x,int y){
    x = find(x);
    y = find(y);
    if(x!=y){
        cnt[x] += cnt[y];
    }
    f[y]=x;//将y合并到x上
}
int main(){
    int i,j,n,m;
    cin>>n>>m;
    for(i = 1;i<=n;i++){
        f[i] = i;
        cnt[i] = 1;
    }
    for(i = 1;i<=m;i++){
        char c[4];int a,b;
        scanf("%s %d",c,&a);
        if(c[0] == 'C'){
            cin>>b;
            merge(a,b); //合并
        }else if(c[1] == '1'){
            cin>>b; //查询是否连通
            if(find(a) == find(b)) cout<<"Yes"<<endl;
            else    cout<<"No"<<endl;
        }else{
            //查询连通块的个数
            cout<<cnt[find(a)]<<endl;
        }
    }
    return 0;
}

(三)树状数组

1.单点更改,区间查询

int n,m;
int c[N],a[N];
int lowbit(int x){
	return x & (-x);
}
void add(int x,int y){
	for(int i = x;i<=n;i+=lowbit(i))
		c[i] += y;
}
int ask(int x){
	int res = 0;
	for(int i = x;i>0;i -= lowbit(i))
		res += c[i];
	return res;
}
int main(){
	int i,j;
	cin>>n>>m;
	for(i = 1;i<=n;i++){
		scanf("%d ",&a[i]);
		add(i,a[i]);
	} 
	for(i = 1;i<=m;i++){
		int a,b,c;
		scanf("%d %d %d",&a,&b,&c);
		if(a == 1){	//更新操作
			add(b,c); 
		}else{	//区间查询操作 
			int t = ask(c) - ask(b-1);
			printf("%d\n",t);
		}
	}
	return 0;
} 

2.区间更改,单点查询

int a[N];
int b[N],c[N];
int n,m;
int lowbit(int x){
    return x&(-x);
}
void add(int x,int y){
    int i,j;
    for(i = x;i<=n;i+=lowbit(i))
        c[i] += y;
}
int sum(int x){
    int i,res = 0;
    for(i = x;i>0;i-=lowbit(i))
        res += c[i];
    return res;
}
int main(){
    int i,j;
    ios::sync_with_stdio(0);
    cin>>n>>m;
    for(i = 1;i<=n;i++){
        cin>>a[i];
        b[i] = a[i]-a[i-1];
        add(i,b[i]);
    }
    for(i = 1;i<=m;i++){
        string s;
        cin>>s;
        if(s[0] == 'C'){
            int l,r,d;
            cin>>l>>r>>d;
            add(l,d);add(r+1,-d);
        }else{
            int t;
            cin>>t;
            cout<<sum(t)<<endl;
        }
    }
    return 0;
}

3.区间更改,区间查询

int n,m;
ll b[N],a[N],c1[N],c2[N];
int lowbit(int x){
    return x & (-x);
}
void add(ll x,ll y){
    for(int i = x;i<=n;i+=lowbit(i)){
        c1[i] += y;
        c2[i] += y*x;
    }
}
ll ask(ll x){
    ll i,res = 0;
    for(i = x;i>0;i-=lowbit(i))
        res += (x+1)*c1[i] - c2[i];
    return res;
}
int main(){
    int i,j;
    ios::sync_with_stdio(0);
    cin>>n>>m;
    for(i = 1;i<=n;i++){
        cin>>a[i];
        b[i] = a[i]-a[i-1];
        add(i,b[i]);
    }
    for(i = 1;i<=m;i++){
        string s;ll l,r,d;
        cin>>s;
        if(s[0] == 'C'){//区间修改
            cin>>l>>r>>d;
            add(l,d);
            add(r+1,-d);
        }else{//区间查询
            cin>>l>>r;
            cout<<ask(r)-ask(l-1)<<endl;
        }
    }
    return 0;
}

4.二维树状数组

int lowbit(int x){
	return x&(-x);
}
void update(int x,int y,int v){
	int i,j;
	for(i = x;i<=n;i+=lowbit(i)){
		for(j = y;j<=n;j+=lowbit(j)){
			tr[i][j] += v;
		}
	}
}
int query(int x,int y){
	int i,j;
	int res = 0;
	for(i = x;i>0;i-=lowbit(i)){
		for(j = y;j>0;j-=lowbit(j)){
			res+=tr[i][j];
		}
	}
	return res;
}

(四)线段树

1.单点更改,区间查询

struct Node{
	int l,r;//节点的左右区间 
	int sum;//区间的和 
}tr[4*N];
int n,m;
int a[N];
//利用子节点信息更新父节点信息 
void pushup(int u){
	tr[u].sum = tr[2*u+1].sum + tr[2*u].sum; 
}
//递归方式建树 
void build(int u,int l,int r){
	//u表示当前需要建立的结点,l为当前建立区间的左端点,r为右端点
	tr[u].l = l,tr[u].r = r;
	if(l == r){	//叶子节点 
		tr[u].sum = a[l];
	}else{
		int mid = l+r>>1;
		//左节点的区间是[l,mid],右节点的区间是[mid+1,r]; 
		build(u<<1,l,mid);	//递归构造左节点 
		build(u<<1|1,mid+1,r);	//递归构造右节点 
		pushup(u);	//更新父节点 
	} 
}
//u为结点下标,x为下标,v为要加上的值 
void update(int u,int x,int v){
	if(tr[u].l == tr[u].r){
		//找到叶子节点,更新原数组和线段树数组 
		tr[u].sum += v,a[l] += v;
	}else{
		int mid = tr[u].l + tr[u].r >> 1;
		if(x <= mid){
			//如果需要更新的节点在左子树区间 
			update(2*u,x,v);
		}else{
			update(2*u+1,x,v);
		}
		pushup(u);	//更新父节点的值 
	}
}
int query(int u,int l,int r){
	//[l,r]为待查询区间 
	if(tr[u].l >= l && tr[u].r <= r){
		return tr[u].sum;
	}else{
		int res = 0;
		int mid = tr[u].l + tr[u].r >>1;
		if(l<=mid){
			//如果左节点的区间和待查询区间有交集
			res += query(2*u,l,r); 
		}
		if(r>mid){
			//如果右节点区间和待查询区间有交集
			res += query(2*u+1,l,r); 
		}
		return res;
	}
}
int main(){
	int i,j;
	ios::sync_with_stdio(0);
	cin>>n>>m;
	for(i = 1;i<=n;i++)
		cin>>a[i];
	//建树
	build(1,1,n);
	for(i = 1;i<=m;i++){
		int a,b,c;
		cin>>a>>b>>c;
		if(a == 1){
			//更新操作 
			update(1,b,c);
		}else{
			//查询操作
			cout<<query(1,b,c)<<endl;
		}
	}
	return 0;
 }

2.区间更改,区间查询

struct Node{
	int l,r;
	ll sum,add;
}tr[4*N];
ll a[N];
int n,m;

void pushup(int u){
	tr[u].sum = tr[u*2].sum + tr[2*u+1].sum; 
}

void build(int u,int l,int r){
	tr[u].l = l;tr[u].r = r;
	if(l == r){
		tr[u].sum = a[l];
		tr[u].add = 0;
	}else{
		int mid = l+r>>1; 
		build(2*u,l,mid);
		build(2*u+1,mid+1,r);
		pushup(u); 
	}
}

void pushdown(int u){
	tr[u*2].sum += tr[u].add*(tr[2*u].r-tr[2*u].l+1);
	tr[u*2+1].sum += tr[u].add*(tr[2*u+1].r-tr[2*u+1].l+1);
	tr[2*u].add += tr[u].add;
	tr[2*u+1].add += tr[u].add;
	tr[u].add = 0;//注意归0 
}

void update(int u,int l,int r,ll v){
	if(l<=tr[u].l &&tr[u].r<=r){
		tr[u].sum +=  v*(tr[u].r-tr[u].l + 1);
		tr[u].add += v;//懒惰标记 
	}else{
		pushdown(u);//每次区间分裂需要pushdown 
		int mid = tr[u].l + tr[u].r >> 1;
		if(l<=mid)
			update(u*2,l,r,v);
		if(r>mid)
			update(u*2+1,l,r,v);
		pushup(u);
	}
}

ll query(int u,int l,int r){
	if(l<=tr[u].l && tr[u].r<=r){
		return tr[u].sum;
	}else{
		pushdown(u);
		ll res = 0;
		int mid = tr[u].l + tr[u].r >> 1;
		if(l<=mid)
			res += query(u*2,l,r);
		if(r>mid)
			res += query(2*u+1,l,r);
		return res;
	} 
}


int main(){
	int i,j;
	ios::sync_with_stdio(0);
	cin>>n>>m;
	for(i=1;i<=n;i++)
		cin>>a[i];
	build(1,1,n);
	for(i = 1;i<=m;i++){
		int o,l,r;
		cin>>o>>l>>r;
		if(o == 1){
			ll d;
			cin>>d;
			update(1,l,r,d);
		}else{
			cout<<query(1,l,r)<<endl;
		}
	}
	return 0;
}

(五)主席树(区间第k小数模板)

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

const int N = 200005;
const int M = 200005;
int n,m;
int a[N],idx;
vector<int> nums;//去重数组 
struct Node{
    int l,r;//左右子节点编号 
    int cnt; 
}tr[N*4+N*18];//开N*4+N*lgN个空间 
int root[N];//一共需要保存N个历史记录

int find(int x){
    int l = 0,r = nums.size()-1;
    while(l!=r){
        int mid = (l+r)/2;
        if(nums[mid] >= x)  r = mid;
        else    l = mid+1;
    }
    return l;
}

int build(int l,int r){//建树,cnt都为0
    int q = ++idx;
    if(l == r)  return q;
    int mid = (l+r)/2;
    tr[q].l = build(l,mid);
    tr[q].r = build(mid+1,r);
    return q;
}
int insert(int p,int l,int r,int x){
    int q = ++idx;
    tr[q] = tr[p];
    if(l == r){
        tr[q].cnt++;
        return q;
    }
    int mid = (l+r)/2;
    //应该更新哪一个值域区间
    if(x<=mid)
        tr[q].l = insert(tr[p].l,l,mid,x);
    else
        tr[q].r = insert(tr[p].r,mid+1,r,x);
    tr[q].cnt = tr[tr[q].l].cnt + tr[tr[q].r].cnt;
    return q;
}

int query(int q,int p,int l,int r,int k){
    if(l == r)  return r;
    //有多少个数落在值域为[l,mid]中 
    int cnt = tr[tr[q].l].cnt - tr[tr[p].l].cnt;
    int mid = (l+r)/2;
    if(k<=cnt)
        return query(tr[q].l,tr[p].l,l,mid,k);
    else
        return query(tr[q].r,tr[p].r,mid+1,r,k-cnt);
}

int main(){
    int i,j;
    cin>>n>>m;
    for(i = 1;i<=n;i++){
        cin>>a[i];
        nums.push_back(a[i]);
    }
    sort(nums.begin(),nums.end());
    nums.erase(unique(nums.begin(),nums.end()),nums.end());
    root[0] = build(0,nums.size()-1);//下标从0开始
    for(i = 1;i<=n;i++){
        root[i] = insert(root[i-1],0,nums.size()-1,find(a[i]));
        //建立可持久话线段树
    }
    for(i = 1;i<=m;i++){
        int l,r,k;
        cin>>l>>r>>k;
        cout<<nums[query(root[r],root[l-1],0,nums.size()-1,k)]<<endl;
    }
    
    return 0;
}

(六)树链剖分

int n,m;
int w[N];//节点的权值
struct Node{
    int v,ne;
}e[N*2];
int h[N],idx;
int siz[N],son[N],de[N],fa[N];
//siz[i]表示以i为跟的子树节点数
//son[i]表示点i的总儿子的节点
//de[i]表示节点i的深度
int id[N],top[N],cnt,pre[N];
//pre[i]表示dfs为i的对应节点的编号
//top[i]表示节点i的重链的定点
//id[i]表示节点i的dfs序,及在线段树中的标号
struct Nod{
    int l,r;
    ll add,sum;
}tr[N*4];
void add(int a,int b){
    e[idx].ne = h[a],e[idx].v = b,h[a] = idx++;
}
//预处理fa,siz,son
void dfs1(int u,int last,int deep){
    int i,j;
    fa[u] = last,de[u] = deep;
    siz[u] = 1;
    int t = -1;
    for(i = h[u];i!=-1;i = e[i].ne){
        int v = e[i].v;
        if(v == last)  continue;
        dfs1(v,u,deep+1);
        siz[u] += siz[v];
        //记录重儿子
        if(t == -1 || siz[t]<siz[v]){
            t = v;
        }
    }
    son[u] = t;
}
//将每个节点标号,t表示u的顶点
void dfs2(int u,int t){
    int i,j;
    id[u] = ++cnt;
    top[u] = t;
    pre[cnt] = u;
    if(son[u] == -1)    return;
    //先遍历重儿子
    dfs2(son[u],t);
    for(i = h[u];i!=-1;i = e[i].ne){
        int v = e[i].v;
        if(v == fa[u] || v == son[u])   continue;
        dfs2(v,v);
    }
}
//线段树
void pushup(int u){
    tr[u].sum = tr[u*2].sum + tr[u*2+1].sum;
}
void build(int u,int l,int r){
    tr[u].l = l,tr[u].r = r;
    if(l == r){
        tr[u].sum = w[pre[l]];
    }else{
        int mid = (l+r)/2;
        build(u*2,l,mid);
        build(u*2+1,mid+1,r);
        pushup(u);
    }
}
void pushdown(int u){
    int d = tr[u].add;
    tr[u*2].sum += (tr[u*2].r-tr[u*2].l+1)*d;
    tr[u*2+1].sum += (tr[u*2+1].r-tr[u*2+1].l+1)*d;
    tr[u*2].add += d;
    tr[u*2+1].add += d;
    tr[u].add = 0;
}
void update(int u,int l,int r,int d){
    if(l<=tr[u].l && tr[u].r<=r){
        tr[u].sum += (tr[u].r-tr[u].l+1)*d;
        tr[u].add += d;
    }else{
        pushdown(u);
        int mid = (tr[u].l+tr[u].r)/2;
        if(l<=mid)  update(u*2,l,r,d);
        if(r>mid)   update(u*2+1,l,r,d);
        pushup(u);
    }
}
ll query(int u,int l,int r){
    if(l<=tr[u].l && tr[u].r<=r){
        return tr[u].sum;
    }else{
        pushdown(u);
        int mid = (tr[u].l+tr[u].r)/2;
        ll res = 0;
        if(l<=mid)  res += query(u*2,l,r);
        if(r>mid)   res += query(u*2+1,l,r);
        return res;
    }
}
void update_path(int u,int v,int d){
    while(top[u] != top[v]){
        //跳顶部深度高的
        if(de[top[u]]<de[top[v]]) swap(u,v);
        update(1,id[top[u]],id[u],d);
        u = fa[top[u]];
    }
    if(de[u]>de[v]) swap(u,v);
    update(1,id[u],id[v],d);
}
ll query_path(int u,int v){
    ll res = 0;
    while(top[u] != top[v]){
        if(de[top[u]] < de[top[v]]) swap(u,v);
        res += query(1,id[top[u]],id[u]);
        u = fa[top[u]];
    }
    if(de[u]>de[v]) swap(u,v);
    res += query(1,id[u],id[v]);
    return res;
}
int main(){
    int i,j;
    cin>>n;
    for(i = 1;i<=n;i++){
        cin>>w[i];
    }
    memset(h,-1,sizeof h);
    for(i = 1;i<n;i++){
        int a,b;
        cin>>a>>b;
        add(a,b);
        add(b,a);
    }
    //进行两次dfs
    int root = 1;//1为根节点
    dfs1(root,-1,1);
    dfs2(root,root);
    cin>>m;//m次操作
    build(1,1,n);
    for(i = 1;i<=m;i++){
        //每一次的操作
        int op;
        cin>>op;
        int u,v,d;
        if(op == 1){
            //修改路径
            cin>>u>>v>>d;
            update_path(u,v,d);
        }else if(op == 2){
            //修改子树
            cin>>u>>d;
            update(1,id[u],siz[u]+id[u]-1,d);
        }else if(op == 3){
            //查询路径
            cin>>u>>v;
            cout<<query_path(u,v)<<endl;
        }else{
            //查询子树
            cin>>u;
            cout<<query(1,id[u],siz[u]+id[u]-1)<<endl;
        }
    }
    return 0;
}

(七)单调栈和单调队列

//单调栈:输出左边第一个比它小的数,如果没有输出-1
int s[N];
int top;
int main(){
int n,i,j,a;
cin>>n;
    for(i = 1;i<=n;i++){
        cin>>a;
        while(top > 0 && s[top] >= a)
            top--;
        if(top == 0)    cout<<"-1 ";
        else    cout<<s[top]<<" ";
        s[++top] = a;
    }
    return 0;
}
//滑动窗口维护区间的最小值
int q[N];
int l,r;
int main(){
    int i,j;
    int k;//滑动窗口大小
    cin>>n>>k;
    for(i =1;i<=n;i++)    cin>>a[i];
    for(i = 1;i<=n;i++){
        if(l<r && i-q[l+1] >= k) l++;
        while(l<r && a[q[r]]>a[i])    r--;
        q[++r]=i;
        if(i>=k)    
            cout<<a[q[l+1]]<<" ";
    }
    return 0;
}

(八)基础莫队

#include<bits/stdc++.h>
using namespace std;
const int N = 1000005;
int len;//每一块的长度
int get(int i){
    return i/len;
}
struct Node{
    int l,r;
    int id;
}q[N];//记录每一个询问
bool cmp1(const Node & A,const Node &B){
    if(get(A.l) == get(B.l)){
        //块内按照右端点排序//块外按照块的升序排序
        return A.r<B.r;
    }else{
        //块外按照块的升序排序
        return get(A.l)<get(B.l);
    }
}
//最好初始化为1
int l = 1,r;
int a[N],cnt[N],res,ans[N];
void add(int x){
    if(cnt[x] == 0) cnt[x]++,res++;
    else cnt[x] ++;
}
void del(int x){
    if(cnt[x] == 1) cnt[x]--,res--;
    else cnt[x]--;
}
int main(){
    int i,j;
    int n,m;
    cin>>n;
    for(i = 1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    cin>>m;
    for(i = 1;i<=m;i++){
        scanf("%d %d",&q[i].l,&q[i].r);
        q[i].id = i;
    }
    len = sqrt(n);
    sort(q+1,q+1+m,cmp1);
    for(i = 1;i<=m;i++){
        while(r<q[i].r) add(a[++r]);
        while(r>q[i].r) del(a[r--]);
        while(l<q[i].l) del(a[l++]);
        while(l>q[i].l) add(a[--l]);
        ans[q[i].id] = res;
    }
    for(i = 1;i<=m;i++) printf("%d\n",ans[i]);
    return 0;
}

(九)点分治

#include<bits/stdc++.h>
using namespace std;
const int N = 10005;
int n,m;
struct Node{
    int v,ne,w;
}e[N*2];
int h[N],idx,d[N];//d[i]记录每个点到重心的距离
int st[N];//标记改点是否被删除
vector<int> p,q;//p记录每个子树到重心的距离,
//q记录所有子树到重心的距离
void add(int a,int b,int c){
    e[idx].v = b,e[idx].ne = h[a],e[idx].w = c,h[a] = idx++;
}
int get_size(int u,int fa){
    if(st[u])   return 0;
    int i,j;
    int cnt = 1;
    for(i = h[u];i!=-1;i = e[i].ne){
        int v = e[i].v;
        if(v == fa) continue;
        cnt+=get_size(v,u);
    }
    return cnt;
}
//求以该点为跟的最大子树下雨tot/2的点
//返回以改点为跟的节点数
int get_wc(int u,int fa,int tot,int &wc){
    int i,j;
    if(st[u])   return 0;
    int sum = 1;
    int res = 0;
    for(i = h[u];i!=-1;i = e[i].ne){
        int v = e[i].v;
        if(v == fa) continue;
        int t = get_wc(v,u,tot,wc);
        res = max(res,t);
        sum += t;
    }
    res = max(tot-sum,res);
    if(res<=tot/2)  wc = u;
    return sum;
}
void get_dis(int u,int fa,int dis){
    if(st[u]) return;
    int i,j;
    d[u] = dis;
    p.push_back(dis);
    for(i = h[u];i!=-1;i = e[i].ne){
        int v = e[i].v;
        int w = e[i].w;
        if(v == fa) continue;
        get_dis(v,u,dis+w);
    }
}

//返回两点距离之和小于m的个数
//双指针算法
int get(vector<int> &p){
    int i,j;
    sort(p.begin(),p.end());
    i = 0,j = p.size()-1;
    int res = 0;
    while(i<j){
        if(p[i] + p[j]<=m){
            res += j-i;
            i++;
        }else{
            j--;
        }
    }
    return res;
}
int calc(int u){
    int i,j;
    if(st[u])   return 0;
    int sum = 0;//记录u所连通的点的答案
    q.clear();
    //获取u连通的点的数量
    //求重心
    get_wc(u,-1,get_size(u,-1),u);
    st[u] = 1;//将重心删除
    for(i = h[u];i!=-1;i = e[i].ne){
        int v = e[i].v;
        int w = e[i].w;
        if(st[v])   continue;
        p.clear();
        get_dis(v,-1,w);//求距离
        sum -= get(p);//利用容斥原理
        for(j = 0;j<p.size();j++){
            if(p[j] <= m)   sum++;
            q.push_back(p[j]);
        }
    }
    sum += get(q);
    for(i = h[u];i!=-1;i = e[i].ne){
        int v = e[i].v;
        sum += calc(v);
    }
    return sum;
}
//点分治,求树上两节点间小于等于m的路径数量
int main(){
    int i,j;
    while(cin>>n>>m,n||m){
        //编号0-n-1
        memset(h,-1,sizeof h);
        idx = 0;
        memset(st,0,sizeof st);
        for(i = 1;i<n;i++){
            int a,b,c;
            scanf("%d %d %d",&a,&b,&c);
            add(a,b,c);
            add(b,a,c);
        }
        cout<<calc(0)<<endl;
    }
    return 0;
}

四、字符串

(一)Kmp算法

int nex[100005];
char s1[1000005]; //文本串 
char s2[100005]; //模式串 
int n,m;//文本串和模式串的长度 
//下标从1开始
void getnex(){
	nex[1] = 0;
	int i,j = 0; 
	for(i = 2;i<=m;i++){
		while(j>0 && s2[j+1]!=s2[i])j = nex[j];
		//如果回跳到第一个字符就不 用再回跳了
		if(s2[j+1] == s2[i])j++;
		nex[i] = j;
	}
} 
int kmp(){
	int i,j=0;
	getnex();
	for(i = 1;i<=n;i++){
		while(s1[i]!=s2[j+1] && j>0)j = nex[j];
		//如果失配 ,那么就不断向回跳,直到可以继续匹配
		if(s1[i] == s2[j+1])j++;
		//如果匹配成功,那么对应的模式串位置++ 
		if(j == m){
		    //输出所有出现位置的起始下标
			printf("%d ",i-m+1);
			//注意下标,是从0还是1
			j = nex[j];//继续匹配 
		} 
	}
} 

(二)字典树

int son[N][26], cnt[N], idx;
// 0号点既是根节点,又是空节点
// son[][]存储树中每个节点的子节点
// cnt[]存储以每个节点结尾的单词数量

// 插入一个字符串
void insert(char *str)
{
    int p = 0;
    for (int i = 0; str[i]; i ++ )
    {
        int u = str[i] - 'a';
        if (!son[p][u]) son[p][u] = ++ idx;
        p = son[p][u];
    }
    cnt[p] ++ ;
}

// 查询字符串出现的次数
int query(char *str)
{
    int p = 0;
    for (int i = 0; str[i]; i ++ )
    {
        int u = str[i] - 'a';
        if (!son[p][u]) return 0;
        p = son[p][u];
    }
    return cnt[p];
}

//插入二进制数
void insert(int v){
    int i,j;
    int p = 0;
    for(i = 30;i>=0;i--){
        int u = v>>i&1;
        if(son[p][u] == 0)
            son[p][u] = ++idx;
        p = son[p][u];
    }
    val[p] = v;
}

(三)字符串hash

ull base = 131,hash[100008],p[100008]; string s;
//求每个前缀的hash
void get_hashs(ull hashs[]){
	p[0] = 1;
	for(int i = 1;i<=n;i++){
		hashs[i] = hashs[i-1]*base+s[i]-'a'+1;
		p[i] = p[i-1]*base;
	}
}
//得到每个子串的hash
ull get_hash(int l,int r,ull hashs[]){
	return hashs[r]-hashs[l-1]*p[r-l+1];
}

五、STL

1.vector

v.push_back(item):向v后加一个元素O(1)
v.pop_back():删除v最后一个元素O(1)
v.size():获取v中元素个数O(1)
v.resize(n):把v的长度重新设定为n个元素O(|n-size|)
v.empty():判断v是否为空O(1)
v.clear():清空v中的元素O(size)
v[index]:获取v中下标为index的元素O(1)
v.begin();返回指向第一个元素的迭代器
v.end();返回指向vector末尾(最后一个元素之后的那个位置)的迭代器
v.front();返回第一个元素
v.back();返回最后一个元素
v.erase(iterator loc);删除loc所指元素并返回下一元素迭代器
v.erase(iterator start, iterator end);删除[start, end)之间的元素,并返回最后一个被删除元素的下个元素的迭代器
去重操作:vt.erase(unique(vt.begin(), vt.end()), vt.end());

2.priority_queue优先队列(默认为大根堆)

pq.push(item):添加元素 O(logn)
pq.pop():使优先级最高的出队O(logn)
pq.top():获取优先级最高的元素O(1)
pq.size():获取元素个数O(1)
pq.empty():是否为空O(1)
优先队列的定义:
priority_queue<int> q1; //默认从大到小,大顶堆 
priority_queue<int ,vector<int>,less<int> > q2; //降序队列,大顶堆 
priority_queue<int ,vector<int>,greater<int> > q3; //升序队列,小顶堆
对于结构体定义:
struct T1{//法一 强烈推荐 
	int x,y;
	friend bool operator < (T1 a,T1 b){
		return a.x<b.x;
	}
};
priority_queue<T1> q1;

struct T1{//法二
	int x,y;
	bool operator < (const T1 &a) const{
		return x<a.x; //大顶堆 
	}
};
priority_queue<T1> q1; 
struct T2{int x,y;};//法三
bool operator <(T2 a,T2 b){
	return a.x>b.x;
}
priority_queue<T2> q2;

struct T3{int x,y;};//法四
struct tmp{ //重写仿函数 
	bool operator() (T3 a,T3 b){
		return a.x<b.x; //大顶堆 
	} 
};
priority_queue<T3, vector<T3>,tmp> q3

3.set(二叉搜索树)

s.insert(item): 插入元素O(logn)
s.size():获取元素的个数O(1)
s.empty():判断是否为空O(1)
s.clear():清空s O(n)
s.find(item):在s中查找item并返回其iterator(迭代器),找不到的话返回s.end()O(logn)
s.count(item):返回s中item的数量,因为集合中的元素不能重复,因此只能返回01 O(logn)
s.erase(it):删除s中it指向位置的元素
s.erase(item):删除s中值为item的元素
s.earse(it1,it2);删除[it1,it2) 这个区间(迭代器)对应位置的元素
iterator lower_bound( const key_type &key ); 返回一个迭代器,指向键值 >= key的第一个元素。
iterator upper_bound( const key_type &key ); 返回一个迭代器,指向键值 > key的第一个元素。
Set的定义:
 
struct T1{
	int key,value;
	bool operator < (const T1 &a) const {
		return key<a.key;//按照升序排列 
	}
};

struct T2{
	int key,value;
};
struct T2cmp{
	bool operator () (const int &a,const int &b){
		if(abs(a-b)<=k)
			return false;
		return a<b;
	}
}; 

int main(){
	int i,j;
	set<T1> s1;
	set<T2,T2cmp> s2;
	set<string> ss1;//等于set<string,less<int> > ss1;从小到大
	set<string,greater<string> > ss2;//从大到小
	set<string,greater<string> > ::iterator itsey;
	//set的遍历
	set<string> :: iterator it; 
	for(it = ss1.begin();it!=ss1.end();it++){
		cout<<*it<<endl;
	}
	return 0;
}
Multiset
c.erase(elem) 删除与elem相等的所有元素,返回被移除的元素个数。
c.erase(pos) 移除迭代器pos所指位置元素,无返回值。

4.map

mp.size():获取元素个数O(1)
mp.empty():判断是否为空O(1)
mp.clear():清空mp O(1) 
mp.find(key):在map中查找key并返回其iterator,找不到的话返回mp.end() O(logn)
mp.count(key):在map中找key的数量,由于每个key都是唯一的,只会返回01
mp[key] 可以直接访问到键值队key---value中的value,如果不存在这样的键值对,那么mp[key]返回的是value类型默认构造器所构造的值,并将该键值对插入到map中
mp[key]=tmp:可以把键值对key---value中的value赋值为tmp,如果没有对应的键值对,则将该键值对插入到map中复杂度: O(logn)
mp.insert(make_pair(key,value)):在mp中插入键值对key----value。一般不这样用,想要插入键值对的话直接使用mp[key]=value即可,map已经对[]运算符重载过了.
哈希表:
#include <unordered_map>
unordered_map

5.pair

#include <utility>头文件中
pair<T1, T2> p1; //创建一个空的pair对象(使用默认构造),它的两个元素分别是T1和T2类型,采用值初始化。
pair<T1, T2> p1(v1, v2); //创建一个pair对象,它的两个元素分别是T1和T2类型,其中first成员初始化为v1,second成员初始化为v2。
make_pair(v1, v2); // 以v1和v2的值创建一个新的pair对象,其元素类型分别是v1和v2的类型。
p1 < p2; // 两个pair对象间的小于运算,其定义遵循字典次序:如 p1.first < p2.first 或者 !(p2.first < p1.first) && (p1.second < p2.second) 则返回true。
p1 == p2// 如果两个对象的first和second依次相等,则这两个对象相等;该运算使用元素的==操作符。
p1.first; // 返回对象p1中名为first的公有数据成员。
p1.second; // 返回对象p1中名为second的公有数据成员。

6.Algorithm中常用算法:

  1. unique()去重函数
    功能:”删除”序列中所有相邻的重复元素(只保留一个)。此处的删除,并不是真的删除,而是指重复元素的位置被不重复的元素给占领了(它并没有将重复的元素删除,而是把重复的元素放到数组的最后面藏起来了)由于它”删除”的是相邻的重复元素,所以在使用unique函数之前,一般都会将目标序列进行排序。对于数组而言返回去重后最后一个元素的指针,而其他容器则是返回去重后最后一个元素的迭代器。
    iterator unique(iterator it_1,iterator it_2);
    其中这两个参数表示对容器中[it_1,it_2)范围的元素进行去重,返回值是一个迭代器,它指向的是去重后容器中不重复序列的最后一个元素的下一个元素。
    使用:
    sort(vt.begin(), vt.end());
    vt.erase(unique(vt.begin(), vt.end()), vt.end());

  2. sort()函数
    降序排列sort(s+1,s+1+n,greater());

  3. 求第k小数
    nth_element(a,a+k,a+n),函数只是把下标为k的元素放在了正确位置,对其它元素并没有排序,当然k左边元素都小于等于它,右边元素都大于等于它,所以可以利用这个函数快速定位某个元素

  4. 二分函数:
    lower_bound(起始地址,结束地址,要查找的数值) 返回的是>=x的第一个数的地址。
    upper_bound(起始地址,结束地址,要查找的数值) 返回的是>x的第一个数的地址
    binary_search(起始地址,结束地址,要查找的数值) 返回的是是否存在这么一个数,是一个bool值。

  5. 在从小到大的排序数组中,

  • lower_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
  • upper_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
    在从大到小的排序数组中,重载lower_bound()和upper_bound()
  • lower_bound( begin,end,num,greater() ):从数组的begin位置到end-1位置二分查找第一个小于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
  • upper_bound( begin,end,num,greater() ):从数组的begin位置到end-1位置二分查找第一个小于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标

六、动态规划

单调队列优化的背包:

const int N = 1005;
const int M = 20005;
int v[N],w[N],s[N];//体积,价值,数量
int dp[M],g[M], q[M],n,m;
int main(){
    int i,j,k;
    cin>>n>>m;
    for(i = 1;i<=n;i++){
        cin>>v[i]>>w[i]>>s[i];
    }
    for(i = 1;i<=n;i++){
        //枚举v[i]的每个余数
        memcpy(g,dp,sizeof g);//滚动数组优化
        //g表示dp[i-1][],dp表示dp[i][];
        for(j = 0;j<v[i];j++){
            int l = 0,r = 0;
            for(k = j;k<=m;k+=v[i]){
 			//维护队列中k到k-s[i]*v[i]的最大值的体积
 			//单调队列中最大值存储物品i的数量大于s[i],出队
                if(l<r && (k-q[l+1])/v[i] > s[i]) l++;
                //除去比当前小的元素
                while(l<r&&g[k]>=g[q[l+1]]+(k-q[l+1])/v[i]*w[i])    r--;
                q[++r]=k;
                dp[k] = g[q[l+1]]+(k-q[l+1])/v[i]*w[i];
            }
        }
    }
    cout<<dp[m]<<endl;
    return 0;
}

七、其他

(一)排序

1.快速排序

void quick_sort(int l,int r){
    if(l == r)  return;
    int x = a[(l+r)/2];
    int l1 = l-1,r1 = r+1;
    while(l1<r1){
        do l1++;
        while(a[l1]<x);
        do r1--;
        while(a[r1]>x);
        if(l1<r1)   swap(a[l1],a[r1]);
        
    }
    quick_sort(l,r1);
    quick_sort(r1+1,r);
}

2.归并排序

void merge_sort(int l,int r){
	if(l==r)return;
	int m = (l+r)/2;
	merge_sort(l,m);
	merge_sort(m+1,r);
	int i = l,j = m+1;
	int k = i;
	while(i<=m&&j<=r){
		if(a[i]<a[j])	tmp[k++] = a[i++];
		else	tmp[k++] = a[j++];
	}
	while(i<=m)tmp[k++] = a[i++];
	while(j<=r)tmp[k++] = a[j++];
	for(i = l;i<=r;i++)
		a[i] = tmp[i];
}

3.归并排序求逆序对

#include<bits/stdc++.h>
using namespace std;
const int N=100005;
int a[N],tmp[N];
long long merge_sort(int a[],int l,int r){
    if(l>=r)
    return 0;
    int mid=l+r>>1,i=l,j=mid+1,k=0;
    long long res=merge_sort(a,l,mid)+merge_sort(a,mid+1,r);
    while(i<=mid&&j<=r){
        if(a[i]<=a[j])
        tmp[k++]=a[i++];
        else{
            tmp[k++]=a[j++];
            res+=mid-i+1;
        }
    }
    while(i<=mid)   tmp[k++]=a[i++];
    while(j<=r) tmp[k++]=a[j++];
    for(int i=l,j=0;i<=r;i++,j++)
        a[i]=tmp[j];
    return res;
}
int main(){
    int n;
    cin>>n;
    for(int i=0;i<n;i++)
    cin>>a[i];
    cout<<merge_sort(a,0,n-1);
    return 0;
}

(二)二分

1.整数二分

bool check(int x) {/* ... */} // 检查x是否满足某种性质
// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r){
    while (l < r){
        int mid =(l+r)/2;
        if (check(mid)) r = mid;    // check()判断mid是否满足性质
        else l = mid + 1;
    }
    return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r){
    while (l < r){
        int mid = (l+r+1)/2;
        if (check(mid)) l = mid;
else r = mid - 1;
}
    return l;
} 

2.浮点数二分

bool check(double x) {/* ... */}
 // 检查x是否满足某种性质
double bsearch_3(double l, double r){
    for(int i = 1;i<=100;i++){ //直接循环100次 
    	double mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;
	}
    return l;
}

(三)快速幂和龟速乘

//龟速乘,利于快速幂的思想进行乘 
ll mul(ll n,ll m,ll p){
	ll res = 0;
	while(m){
		if(m%2 == 1){
			res = (res + n)%p;
		}
		n = n*2%p;
		m /= 2;
	} 
	return res;
}
//快速幂
ll power(ll n,ll m,ll p){
	ll ans = 1;
	while(m != 0){
		if(m % 2 == 1)
			ans = ans * n % p;
		n = n * n % p;
		m = m/2;
	}
	return ans;
} 

(四)快读

inline int read(){
    int x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9'){
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9'){
        x = (x<<1) + (x<<3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}

(五)高精度

_int128 a;//可以防止爆longlong,注意两个

1.大数相加:

vector<int> A,B;
vector<int> add(vector<int> &A,vector<int> &B){
	//使用引用的方法使运算更快,copy数组需要时间
	vector<int> C;int t = 0;
	for(int i = 0;i<A.size() || i<B.size();i++){
		if(i<A.size())t+=A[i];	
		if(i<B.size())t+=B[i];
		C.push_back(t%10);
		t/=10;
	}
	if(t!=0)C.push_back(t);
	return C;
}
int main(){
	int i,j,k;
	string s1,s2;
	cin>>s1>>s2;
	for(i = s1.size()-1;i>=0;i--)
		A.push_back(s1[i]-'0');
	for(i = s2.size()-1;i>=0;i--)
		B.push_back(s2[i]-'0');
	vector<int> C = add(A,B);
	for(i = C.size()-1;i>=0;i--)
		printf("%d",C[i]);
	return 0;
}  

2.高精度减:

vector<int> A,B;
//比较函数,如果A>=B返回true 
bool cmp(vector<int> &A,vector<int> &B){
	int i;
	if(A.size()!=B.size())return A.size()>B.size();
	for(i = A.size()-1;i>=0;i--)
		if(A[i]!=B[i])
			return A[i]>B[i];
	return true;//两个相等也返回true 
} 
//保证A>B 
vector<int> sub(vector<int> &A,vector<int> &B){
	int t = 0,i;
	vector<int>C;
	for(i = 0;i<A.size();i++){
		t = A[i]+t;
		if(i<B.size())t-=B[i];
		C.push_back((t+10)%10);//将每一位减的数放进C中 
		if(t<0)t=-1;
		else t = 0;
	}
	while(C.back()==0&&C.size()>1)
		C.pop_back();//去掉前导0 
	return C;
}
int main(){
	int i,j,k;
	string s1,s2;
	cin>>s1>>s2;
	//将字符串倒置放进vector数组,数组中第一个数据为最低位
	for(i = s1.size()-1;i>=0;i--)A.push_back(s1[i]-'0');  
	for(i = s2.size()-1;i>=0;i--)B.push_back(s2[i]-'0');
	vector<int> C;
	if(cmp(A,B))	C = sub(A,B);	
	else{
		C = sub(B,A);	printf("-");
	}
	for(i = C.size()-1;i>=0;i--)	printf("%d",C[i]);
	return 0;
}	

3.高精度除低精度:

vector<int > A;
//b为除数,A为被除数,r为余数 
vector<int> div(vector<int> &A,int &b,int &r){
	vector<int> C;
	r = 0;
	for(int i = A.size()-1;i>=0;i--){
		//除法从最高位开始
		r = r*10+A[i];
		C.push_back(r/b);
		r%=b;
	}
	reverse(C.begin(),C.end());
	while(C.size()>1 && C.back()==0)C.pop_back(); 
	return C;
}
int main(){
	int i,j,k;
	string a;
	int b,r = 0; //r表示余数 
	cin>>a>>b;
	for(i = a.size()-1;i>=0;i--)A.push_back(a[i]-'0');
	vector<int> C = div(A,b,r);
	for(i = C.size()-1;i>=0;i--){
		printf("%d",C[i]);
	}
	printf("\n%d",r); //输出余数 
	return 0;
}

4.高精度乘低精度

vector<int> mul(vector<int> &A,int a){
	vector<int> C;
	int i,t = 0;//t表示进位
	for(i = 0;i<A.size();i++){
		t+=A[i]*a;
		C.push_back(t%10);
		t/=10;
	}
	while(t){
		C.push_back(t%10);
		t/=10;
	}
	while(C.back()==0 && C.size()>1){
	    C.pop_back();//去掉前导0
	}
	return C;
}

int main(){
	int i,j,k;
	string s1;
	int a;
	vector<int> A;
	cin>>s1>>a;
	for(i = s1.size()-1;i>=0;i--)A.push_back(s1[i]-'0');
	vector<int> C = mul(A,a);
	for(i = C.size()-1;i>=0;i--)
		printf("%d",C[i]);
	return 0;
}

5.高精度乘高精度

int t[100005];
string mul(string &s1,string &s2){
	reverse(s1.begin(),s1.end());
	reverse(s2.begin(),s2.end());
	int i,j,l1 = s1.size(),l2 = s2.size();
	for(i = 0;i<l1;i++){
		for(j = 0;j<l2;j++){
			t[i+j] += (s1[i]-'0')*(s2[j]-'0');
		}
	}
	for(i = 0;i<l1+l2-1;i++){//最多为:l1+l2-1位 
		t[i+1]+=t[i]/10;
		t[i]%=10;
	}
	//去掉前导0 
	while(i>0 && t[i] == 0)	i--; 
	string c;
	for(i = i;i>=0;i--){
		c += (t[i]+'0'); //字符串的拼接 
	}
	return c;
}
int main(){
	int i,j,k;
	string s1,s2;
	cin>>s1>>s2;
	string ans = mul(s1,s2);
	cout<<ans;
	return 0;
}

八、Java高精度

(一)java参考代码:

参考一:

//import java.util.Scanner;//相当于输入输出头文件
import java.util.*;//啥都有头文件
//import java.math.BigInteger;//大数类
import java.math.BigDecimal;//大浮点数
import java.math.*;//关于数学的头文件
public class Main {
   public static void main(String[] args) {
       BigInteger[] sum=new BigInteger[105];//开一个数组
       Scanner cin = new Scanner(System.in);//必须要写这句话(输入)
       int t = cin.nextInt();//输入整数T nextInt() 下一个int型数据
//        while(cin.hasNext()) {//循环输入,hasNext()
//             int n = cin.nextInt();
//        }
       while (t--!=0) {//java里不能直接写while(t--),必须是只有真假的表达式
           BigInteger a = cin.nextBigInteger();//输入下一个大数
           BigInteger b = cin.nextBigInteger();//输入下一个大数
           BigInteger c;
           c = a.add(b);//c=a+b
           c = a.subtract(b);//c=a-b
           c = a.multiply(b);//c=a*b
           c = a.divide(b);//c=a/b(整数除法)
           c = a.remainder(b);//c=a%b
           c = a.mod(b);//c=a%b(结果为正数)
           c = BigInteger.ZERO;//c=0
           c = BigInteger.ONE;//c=1
           c = BigInteger.TEN;//c=10 java大数中只有0,1,10
           c = BigInteger.valueOf(7);//c=7
           int n = 7;
           c = BigInteger.valueOf(n);//c=n
           c = a.pow(n);//幂运算 c=pow(a,n)
           c = a.gcd(b);//最大公约数 c=gcd(a,b)
           c = a.abs();//
           c = a.max(b);//c=max(a,b)
           c = a.min(b);//c=min(a,b)
           n = a.compareTo(b);//a<b n=-1;a=b n=0;a>b n=1
           a.equals(b);//a==b
           a.isProbablePrime(2);//判断素数,有一定几率错误,最好手写
           System.out.println(c);//输出c并换行
           System.out.print(c);//输出c
           System.out.println(a+" "+b);//输出a空格b
           BigDecimal d = cin.nextBigDecimal();//输入下一个高精度浮点数
           BigDecimal e = cin.nextBigDecimal();//输入下一个高精度浮点数
           BigDecimal f = d.add(e);//f=d+e
           //...其他和大数类似
           f = d.setScale(2);//保留2位小数,默认四舍五入
           f = d.setScale(2,BigDecimal.ROUND_DOWN);//保留2位小数,向下取整
           f = d.setScale(2,BigDecimal.ROUND_UP);//保留2位小数,向上取整
           f = d.setScale(2,BigDecimal.ROUND_HALF_DOWN);//保留2位小数,四舍五入,正好.5舍去
           f = d.setScale(2,BigDecimal.ROUND_HALF_UP);//保留2位小数,四舍五入,正好.5进位
       }
   }
}

参考二:

import java.util.*;
import java.io.BufferedInputStream;
import java.math.*;
public class Main {
	
	static int N = 2005;
    static BigInteger mod = new BigInteger("1000000007");
    static int n;
    static BigInteger[] a = new BigInteger[N];
    static BigInteger[] dp = new BigInteger[N];
	public static void main(String args[]) {
		Scanner cin = new Scanner(System.in);
		int T;
		BigInteger a = new BigInteger("123456789") ;	// 声明BigInteger对象
		BigInteger b = new BigInteger("987654321") ;	// 声明BigInteger对象
		System.out.println("加法操作:" + b.add(a)) ;	// 加法操作
		System.out.println("减法操作:" + b.subtract(a)) ;	// 减法操作
		System.out.println("乘法操作:" + b.multiply(a)) ;	// 乘法操作
		System.out.println("除法操作:" + b.divide(a)) ;	// 除法操作
		System.out.println("最大数:" + b.max(a)) ;	 // 求出最大数
		System.out.println("最小数:" + b.min(a)) ;	 // 求出最小数
		System.out.println("商:"+b.divide(a));//求商
		System.out.println("余数"+b.mod(a));//求余数
		BigInteger result[] = b.divideAndRemainder(a) ;	// 求出余数的除法操作
		System.out.println("商是:" + result[0] + 
			";余数是:" + result[1]) ;
		
		//比较函数
		if( a.compareTo(b) == 0 ) System.out.println("a == b"); //大整数a==b
		else if( a.compareTo(b) > 0 ) System.out.println("a > b"); //大整数a>b
		else if( a.compareTo(b) < 0 ) System.out.println("a < b"); //大整数a<b
		
		int n;
		BigInteger m;
		n=cin.nextInt(); //读入一个int;
		m=cin.nextBigInteger();//读入一个BigInteger;
		m=BigInteger.valueOf(n);//将n转换成大整数
	}
}

import java.math.BigInteger;
import java.util.Scanner;
 
public class Main {
    public static void main(String args[]) {
        // read
        Scanner in = new Scanner(System.in);
        BigInteger a = in.nextBigInteger();
 
        // 3 special numbers
        a = BigInteger.ZERO;
        a = BigInteger.ONE;
        a = BigInteger.TEN;
 
        // convert an int to BigInteger
        BigInteger b = BigInteger.valueOf(2333);
        BigInteger p = BigInteger.valueOf(998244353);
 
        // convert a BigInteger to int
        int i = b.intValue();
        // convert a BigInteger to long
        long l = b.longValue();
 
        // operations:
        // a + b;                                BigInteger add(BigInteger b);
        a.add(b);
        // a - b;                                BigInteger subtract(BigInteger b);
        a.subtract(b);
        // a * b;                                BigInteger multiply(BigInteger b);
        a.multiply(b);
        // a / b;                                BigInteger divide(BigInteger b);
        a.divide(b);
        // a % b;                                BigInteger mod(BigInteger b);
        a.mod(b);
        // -a;                                   BigInteger negate();
        a.negate();
        // a < 0 ? -1 : (a > 0 ? 1 : 0);         int signum();
        a.signum();
        // signum(a - b);                        int compareTo(BigInteger b);
        a.compareTo(b);
        // a == b;                               boolean equals(BigInteger b);
        a.equals(b);
 
        // abs(a);                               BigInteger abs();
        a.abs();
        // max(a, b);                            BigInteger max(BigInteger b);
        a.max(b);
        // min(a, b);                            BigInteger min(BigInteger b);
        a.min(b);
        // gcd(a, b);                            BigInteger gcd(BigInteger b);
        a.gcd(b);
        // pow(a, i);                            BigInteger pow(int i);
        a.pow(i);
 
        // modPow(a, b, p);              BigInteger modPow(BigInteger b, BigInteger p);
        a.modPow(b, p);
        // modPow(a, p - 2, p);                  BigInteger modInverse(BigInteger p);
        a.modInverse(p);
        // isPrime(a); (probability:1 - 0.5^i)  boolean isProbablePrime(int certainty);
        a.isProbablePrime(i);
 
        // a << i;                               BigInteger shiftLeft(int i);
        a.shiftLeft(i);
        // a >> i;                               BigInteger shiftRight(int i);
        a.shiftRight(i);
        // a ^ b;                                BigInteger xor(BigInteger b);
        a.xor(b);
        // a | b;                                BigInteger or(BigInteger b);
        a.or(b);
        // a & b;                                BigInteger and(BigInteger b);
        a.and(b);
        // ~a;                                   BigInteger not();
        a.not();
        // a & ~b;                               BigInteger andNot(BigInteger b);
        a.andNot(b);
 
        // ((a >> i) & 1) == 1;                  BigInteger testBit(int i);
        a.testBit(i);
        // a | (1 << i);                         BigInteger setBit(int i);
        a.setBit(i);
        // a & ~(1 << i);                        BigInteger clearBit(int i);
        a.clearBit(i);
        // a ^ (1 << i);                         BigInteger flipBit(int i);
        a.flipBit(i);
        // a & -a;                               BigInteger getLowerSetBit();
        a.getLowestSetBit();
        // __builtin_popcount(a);                int bitCount();
        a.bitCount();
        // ceil(log2(this < 0 ? -this : this+1)) int bitLength();
        a.bitLength();
    }
} 

Java过题代码:

import java.math.BigInteger;
import java.util.Scanner;
public class Main {
	static int mod = 1000;
	static int N = 10005;
	static BigInteger[][] f = new BigInteger[1005][105];
	static BigInteger Cnm(int n,int m) {
		for(int i = 0;i<=n;i++) {
			f[i][0] = new BigInteger("1");
		}
		for(int i = 1;i<=n;i++) {
			for(int j = 1;j<=m && j<=i;j++) {
				if(i == j)	f[i][j] = new BigInteger("1");
				else {
					f[i][j] = new BigInteger("0");
					f[i][j] = f[i-1][j].add(f[i-1][j-1]);
				}
			}
		}
		return f[n][m];
	}
	static int qmi(int a,int b) {
		int res = 1;
		while(b!=0) {
			if(b%2 == 1)	res = (int) ((long)res*a%mod);
			a = (int) ((long)a*a%mod);
			b = b/2;
		}
		return res;
	}
	public static void main(String[] args) {
		int x,k;
		Scanner cin = new Scanner(System.in);
		k = cin.nextInt();
		x = cin.nextInt();
		x = qmi(x,x);
		System.out.println(Cnm(x-1,k-1));
	}
}

(二)Java快速读入输出模板

import java.io.*;
import java.util.*;
public class Main {
    public static void main(String[] args) {
        InputStream inputStream = System.in;
        OutputStream outputStream = System.out;
        InputReader in = new InputReader(inputStream);
        PrintWriter out = new PrintWriter(outputStream);
        Solver solver = new Solver();
        solver.solve(in, out);
        out.close();
    }
    static class Solver {
        public void solve(InputReader in, PrintWriter out) {
 			//写执行代码
        }
    }
    static class InputReader {
        public BufferedReader reader;
        public StringTokenizer tokenizer;
        public InputReader(InputStream stream) {
            reader = new BufferedReader(new InputStreamReader(stream), 32768);
            tokenizer = null;
        }
        public String next() {
            while (tokenizer == null || !tokenizer.hasMoreTokens()) {
                try {
                    tokenizer = new StringTokenizer(reader.readLine());
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            return tokenizer.nextToken();
        }
        public int nextInt() {
            return Integer.parseInt(next());
        }
        public long nextLong() {
            return Long.parseLong(next());
        }
    }
}

九、计算几何

(一)基本模板:

参考算法竞赛从入门到进阶

#include <bits/stdc++.h>
using namespace std;
const double pi = acos(-1.0);  //高精度圆周率
const double eps = 1e-8;       //偏差值
const int maxp = 1010;         //点的数量
int sgn(double x){  //判断x是否等于0
    if(fabs(x) < eps)  return 0;
    else return x<0?-1:1;
}
int Dcmp(double x, double y){  //比较两个浮点数:0 相等;-1 小于;1 大于
    if(fabs(x - y) < eps) return 0;
    else return x<y ?-1:1;
}
//---------------平面几何:点和线--------
struct Point{  //定义点和基本运算
    double x,y;
    Point(){}
    Point(double x,double y):x(x),y(y){}
    Point operator + (Point B){return Point(x+B.x,y+B.y);}
    Point operator - (Point B){return Point(x-B.x,y-B.y);}
    Point operator * (double k){return Point(x*k,y*k);}    //长度增大k倍
    Point operator / (double k){return Point(x/k,y/k);}    //长度缩小k倍
    bool operator == (Point B){return sgn(x-B.x)==0 && sgn(y-B.y)==0;}
    bool operator < (Point B){return sgn(x-B.x)<0 || (sgn(x-B.x)==0 && sgn(y-B.y)<0);} //用于凸包
};
typedef Point Vector;   //定义向量
//点积
double Dot(Vector A,Vector B){return A.x*B.x + A.y*B.y;}
//向量的长度
double Len(Vector A){return sqrt(Dot(A,A));}   
//向量长度的平方
double Len2(Vector A){return Dot(A,A);}
//求向量A和B的夹角
double Angle(Vector A,Vector B){return acos(Dot(A,B)/Len(A)/Len(B));}
//叉积,大于0则B在A的逆时针方向;小于0则B在A的顺时针方向;等于0则B与A共线,可能是同方向的,也可能是反方向的
double Cross(Vector A,Vector B){return A.x*B.y - A.y*B.x;}
//三角形ABC面积的2倍
double Area2(Point A, Point B, Point C){return Cross(B-A, C-A);} 
//两点的距离,方法一
double Distance(Point A, Point B){return hypot(A.x-B.x,A.y-B.y);} 
//两点的距离,方法二
double Dist(Point A,Point B){return sqrt((A.x-B.x)*(A.x-B.x) + (A.y-B.y)*(A.y-B.y));}
//向量A的单位法向量
Vector Normal(Vector A){return Vector(-A.y/ Len(A), A.x/ Len(A));}

bool Parallel(Vector A, Vector B){return sgn(Cross(A,B)) == 0;}//向量平行或重合

//向量A逆时针旋转rad度
Vector Rotate(Vector A, double rad){  
    return Vector(A.x*cos(rad)-A.y*sin(rad), A.x*sin(rad)+A.y*cos(rad));
}

struct Line{
    Point p1,p2;//线上的两个点
    Line(){}
    Line(Point p1,Point p2):p1(p1),p2(p2){}
   // Line(Point x,Point y){p1 = x;p2 = y;}
  //  Point(double x,double y):x(x),y(y){}
//根据一个点和倾斜角 angle 确定直线,0<=angle<pi
    Line(Point p,double angle){
        p1 = p;
        if(sgn(angle - pi/2) == 0){p2 = (p1 + Point(0,1));}
        else{p2 = (p1 + Point(1,tan(angle)));}
    }
//ax+by+c=0
    Line(double a,double b,double c){
        if(sgn(a) == 0){
            p1 = Point(0,-c/b);
            p2 = Point(1,-c/b);
        }
        else if(sgn(b) == 0){
            p1 = Point(-c/a,0);
            p2 = Point(-c/a,1);
        }
        else{
            p1 = Point(0,-c/b);
            p2 = Point(1,(-c-a)/b);
        }
    }
};

typedef Line Segment;   //定义线段,两端点是Point p1,p2

//返回直线倾斜角 0<=angle<pi
double Line_angle(Line v){
    double k = atan2(v.p2.y-v.p1.y, v.p2.x-v.p1.x);
    if(sgn(k) < 0)k += pi;
    if(sgn(k-pi) == 0)k -= pi;
        return k;
}
//点和直线关系:1 在左侧;2 在右侧;0 在直线上
int Point_line_relation(Point p,Line v){
    int c = sgn(Cross(p-v.p1,v.p2-v.p1));
    if(c < 0)return 1;       //1:p在v的左边
    if(c > 0)return 2;       //2:p在v的右边
    return 0;                //0:p在v上
}
// 点和线段的关系:0 点p不在线段v上;1 点p在线段v上。
bool Point_on_seg(Point p, Line v){
    return sgn(Cross(p-v.p1, v.p2-v.p1)) == 0 && sgn(Dot(p - v.p1,p- v.p2)) <= 0;
}

//两直线关系:0 平行,1 重合,2 相交
int Line_relation(Line v1, Line v2){
    if(sgn(Cross(v1.p2-v1.p1,v2.p2-v2.p1)) == 0){
       if(Point_line_relation(v1.p1,v2)==0)  return 1;//1 重合
       else return 0;//0 平行
    }
    return 2; //2 相交
}

 //点到直线的距离
 double Dis_point_line(Point p, Line v){
    return fabs(Cross(p-v.p1,v.p2-v.p1))/Distance(v.p1,v.p2);
 }

//求在直线上的投影点
Point Point_line_proj(Point p, Line v){
    double k=Dot(v.p2-v.p1,p-v.p1)/Len2(v.p2-v.p1);
    return v.p1+(v.p2-v.p1)*k;
}

//点p对直线v的对称点
Point Point_line_symmetry(Point p, Line v){
    Point q = Point_line_proj(p,v);//先求点p在直线v的投影点
    return Point(2*q.x-p.x,2*q.y-p.y);
}

//点到线段的距离
double Dis_point_seg(Point p, Segment v){
    if(sgn(Dot(p- v.p1,v.p2-v.p1))<0 || sgn(Dot(p- v.p2,v.p1-v.p2))<0) //点的投影不在线段上
        return min(Distance(p,v.p1),Distance(p,v.p2));//到两端点的距离取最小值
    return Dis_point_line(p,v); //点的投影在线段上
 }

//求两直线ab和cd的交点
//**调用前要保证两直线不平行或重合,先判断两直线是否平行
 Point Cross_point(Point a,Point b,Point c,Point d){ //Line1:ab,  Line2:cd
    double s1 = Cross(b-a,c-a);
    double s2 = Cross(b-a,d-a);  //叉积有正负
    return Point(c.x*s2-d.x*s1,c.y*s2-d.y*s1)/(s2-s1);
 }

 //两线段是否相交:1 相交,0不相交
 //如果求两线段交点,先判断两线段是否相交,相交则转化成两直线求交点
bool Cross_segment(Point a,Point b,Point c,Point d){//Line1:ab,  Line2:cd
	double c1=Cross(b-a,c-a),c2=Cross(b-a,d-a);
	double d1=Cross(d-c,a-c),d2=Cross(d-c,b-c);
	return sgn(c1)*sgn(c2)<0 && sgn(d1)*sgn(d2)<0;//注意交点是端点的情况不算在内
}

//---------------平面几何:多边形----------------
struct Polygon{
    int n;   //多边形的顶点数
    Point p[maxp];   //多边形的点
    Line v[maxp];    //多边形的边
};

//判断点和任意多边形的关系: 3 点上; 2 边上; 1 内部; 0 外部
//点pt,多边形的点Point *p,多边形点的数量n,多边形顶点按照顺时针或者逆时针方向排列
int Point_in_polygon(Point pt,Point *p,int n){ 
    for(int i = 0;i < n;i++){  //点在多边形的顶点上,下标从0开始
        if(p[i] == pt)return 3;
    }
    for(int i = 0;i < n;i++){//点在多边形的边上
        Line v=Line(p[i],p[(i+1)%n]);
        if(Point_on_seg(pt,v)) return 2;
    }
    int num = 0;
    for(int i = 0;i < n;i++){
        int j = (i+1)% n;
        int c = sgn(Cross(pt-p[j],p[i]-p[j]));
        int u = sgn(p[i].y - pt.y);
        int v = sgn(p[j].y - pt.y);
        if(c > 0 && u < 0 && v >=0) num++;
        if(c < 0 && u >=0 && v < 0) num--;
    }
    return num != 0; //1 内部; 0 外部
}

//求多边形的面积,从原点开始划分三角形,顶点按照顺时针或者逆时针方向排列
double Polygon_area(Point *p, int n){ //Point *p表示多边形。
    double area = 0;
    for(int i = 0;i < n;i++)
        area += Cross(p[i],p[(i+1)%n]);
    return area/2;    //面积有正负,不能简单地取绝对值
}
//求多边形重心。Point *p表示多边形。
Point Polygon_center(Point *p, int n){ 
    Point ans(0,0);
    if(Polygon_area(p,n)==0) return ans;
    for(int i = 0;i < n;i++)
        ans = ans + (p[i]+p[(i+1)%n]) * Cross(p[i],p[(i+1)%n]); //面积有正负
    return ans/Polygon_area(p,n)/6.;
}

//Convex_hull()求凸包。凸包顶点放在ch中,返回值是凸包的顶点数
int Convex_hull(Point *p,int n,Point *ch){
    sort(p,p+n);         //对点排序:按x从小到大排序,如果x相同,按y排序
    n=unique(p,p+n)-p;   //去除重复点
    int v=0;
	//求下凸包。如果p[i]是右拐弯的,这个点不在凸包上,往回退
    for(int i=0;i<n;i++){
        while(v>1 && sgn(Cross(ch[v-1]-ch[v-2],p[i]-ch[v-2]))<=0)
			v--;
        ch[v++]=p[i];
    }
    int j=v;
	//求上凸包
    for(int i=n-2;i>=0;i--){
        while(v>j && sgn(Cross(ch[v-1]-ch[v-2],p[i]-ch[v-2]))<=0)
			v--;
        ch[v++]=p[i];
    }
    if(n>1) v--;
    return v;   //返回值v是凸包的顶点数
}

//---------------平面几何:圆----------------
struct Circle{
    Point c;//圆心
    double r;//半径
    Circle(){}
    Circle(Point c,double r):c(c),r(r){}
    Circle(double x,double y,double _r){c=Point(x,y);r = _r;}
};

//点和圆的关系: 0 点在圆内, 1 圆上, 2 圆外.
int Point_circle_relation(Point p, Circle C){
    double dst = Distance(p,C.c);
    if(sgn(dst - C.r) < 0) return 0;  //点在圆内
    if(sgn(dst - C.r) ==0) return 1;   //圆上
    return 2;   //圆外
}

//直线和圆的关系:0 直线在圆内, 1 直线和圆相切, 2 直线在圆外
int Line_circle_relation(Line v,Circle C){
    double dst = Dis_point_line(C.c,v);
    if(sgn(dst-C.r) < 0) return 0; //直线在圆内
    if(sgn(dst-C.r) ==0) return 1;  //直线和圆相切
    return 2; //直线在圆外
}

//线段和圆的关系:0 线段在圆内, 1 线段和圆相切, 2 线段在圆外
int Seg_circle_relation(Segment v,Circle C){
    double dst = Dis_point_seg(C.c,v);
    if(sgn(dst-C.r) < 0) return 0; //线段在圆内
    if(sgn(dst-C.r) ==0) return 1;  //线段和圆相切
    return 2;    //线段在圆外
}

//直线和圆的交点,pa, pb是交点。返回值是交点个数
int Line_cross_circle(Line v,Circle C,Point &pa,Point &pb){
    if(Line_circle_relation(v, C)==2) return 0;//无交点
    Point q = Point_line_proj(C.c,v);  //圆心在直线上的投影点
    double d = Dis_point_line(C.c,v);   //圆心到直线的距离
    double k = sqrt(C.r*C.r-d*d);   //
    if(sgn(k) == 0){   //1个交点,直线和圆相切
        pa = q;
        pb = q;
        return 1;
    }
    Point n=(v.p2-v.p1)/ Len(v.p2-v.p1);  //单位向量
    pa = q + n*k;
    pb = q - n*k;
    return 2;//2个交点
}

//-------------------三维几何----------------
//三维:点
struct Point3{
    double x,y,z;
    Point3(){}
    Point3(double x,double y,double z):x(x),y(y),z(z){}
    Point3 operator + (Point3 B){return Point3(x+B.x,y+B.y,z+B.z);}
    Point3 operator - (Point3 B){return Point3(x-B.x,y-B.y,z-B.z);}
    Point3 operator * (double k){return Point3(x*k,y*k,z*k);}
    Point3 operator / (double k){return Point3(x/k,y/k,z/k);}
    bool operator == (Point3 B){return sgn(x-B.x)==0 && sgn(y-B.y)==0 && sgn(z-B.z)==0;}
};
typedef Point3 Vector3;
//点积。和二维点积函数同名。C++允许函数同名。
double Dot(Vector3 A,Vector3 B){return A.x*B.x+A.y*B.y+A.z*B.z;}
//叉积
Vector3 Cross(Vector3 A,Vector3 B){return Point3(A.y*B.z-A.z*B.y,A.z*B.x-A.x*B.z,A.x*B.y-A.y*B.x);}
double Len(Vector3 A){return sqrt(Dot(A,A));}   //向量的长度
double Len2(Vector3 A){return Dot(A,A);}        //向量长度的平方
double Distance(Point3 A,Point3 B){
    return sqrt((A.x-B.x)*(A.x-B.x)+(A.y-B.y)*(A.y-B.y)+(A.z-B.z)*(A.z-B.z));
}
double Angle(Vector3 A,Vector3 B){return acos(Dot(A,B)/Len(A)/Len(B));}   //A与B的夹角
//三维:线
struct Line3{
    Point3 p1,p2;
    Line3(){}
    Line3(Point3 p1,Point3 p2):p1(p1),p2(p2){}
};
typedef Line3 Segment3;   //定义线段,两端点是Point p1,p2

//三角形面积的2倍
double Area2(Point3 A,Point3 B,Point3 C){return Len(Cross(B-A, C-A));}

//三维:点到直线距离
double Dis_point_line(Point3 p, Line3 v){
    return Len(Cross(v.p2-v.p1,p-v.p1))/Distance(v.p1,v.p2);
}

//三维:点在直线上
bool Point_line_relation(Point3 p,Line3 v){
    return sgn( Len(Cross(v.p1-p,v.p2-p))) == 0 && sgn(Dot(v.p1-p,v.p2-p))== 0;
}
//三维:点到线段距离。
double Dis_point_seg(Point3 p, Segment3 v){
    if(sgn(Dot(p- v.p1,v.p2-v.p1)) < 0 || sgn(Dot(p- v.p2,v.p1-v.p2)) < 0)
        return min(Distance(p,v.p1),Distance(p,v.p2));
    return Dis_point_line(p,v);
}
//三维:点 p 在直线上的投影
Point3 Point_line_proj(Point3 p, Line3 v){
    double k=Dot(v.p2-v.p1,p-v.p1)/Len2(v.p2-v.p1);
    return v.p1+(v.p2-v.p1)*k;
}
//三维:平面
struct Plane{
    Point3 p1,p2,p3;//平面上的三个点
    Plane(){}
    Plane(Point3 p1,Point3 p2,Point3 p3):p1(p1),p2(p2),p3(p3){}
};
//平面法向量
Point3 Pvec(Point3 A,Point3 B,Point3 C){ return Cross(B-A,C-A);}
Point3 Pvec(Plane f){ return Cross(f.p2-f.p1,f.p3-f.p1);}
//四点共平面
bool Point_on_plane(Point3 A,Point3 B,Point3 C,Point3 D){
    return sgn(Dot(Pvec(A,B,C),D-A)) == 0;
}
//两平面平行
int Parallel(Plane f1, Plane f2){
    return Len(Cross(Pvec(f1),Pvec(f2))) < eps;
}
//两平面垂直
int Vertical (Plane f1, Plane f2){
    return sgn(Dot(Pvec(f1),Pvec(f2)))==0;
}
//直线与平面的交点p,返回值是交点个数  
int Line_cross_plane(Line3 u,Plane f,Point3 &p){
    Point3 v = Pvec(f);
    double x = Dot(v, u.p2-f.p1);
    double y = Dot(v, u.p1-f.p1);
    double d = x-y;
    if(sgn(x) == 0 && sgn(y) == 0) return -1;//-1:v在f上
    if(sgn(d) == 0)return 0;                  //0:v与f平行
    p = ((u.p1 * x)-(u.p2 * y))/d;             //v与f相交
    return 1;
}

//四面体有向体积*6
double volume4(Point3 A,Point3 B,Point3 C,Point3 D){return Dot(Cross(B-A,C-A),D-A);}


int main(){
    Point a(0,1),b(0,0),c(1,1),d(1,2),p(1.5,1);
    double a1=5,b1=6,c1=1;
    Line k(a,b),k2(c,d);
    Point pr(1,1),cr(1,1); double r=1; Circle C(cr,r);

    cout<<endl<<"Line_circle_relation="<<Line_circle_relation(k,C);
    cout<<endl<<"Seg_circle_relation="<<Seg_circle_relation(k,C);
    cout<<endl<<"Point_circle_relation="<<Point_circle_relation(pr,C);
    cout<<endl<<"parallel="<<Parallel(a,b)<<endl;
    cout<<"dot="<<Dot(a,b)<<endl<<" angle="<<Angle(a,b)<<endl;
    cout<<"90:"<<sgn(Rotate(a, -pi/2).x)<<endl<<Rotate(a, -pi/2).y;
    cout<<endl<<"line angle="<<Line_angle(k)*4;
    cout<<endl<<"line place="<<Point_line_relation(p,k);
    cout<<endl<<"point_on_seg="<<Point_on_seg(p,k);
    cout<<endl<<"dis_point_line="<<Dis_point_line(p,k);
    cout<<endl<<"dis_point_line="<<Dis_point_seg(p,k);
    Point pp=Cross_point(a,b,c,d);
    cout<<endl<<"crosspoint="<<pp.x<<"  "<<pp.y;
    cout<<endl<<"cross seg="<<Cross_segment(a,b,c,d);
    cout<<endl<<"distance="<<Distance(a,b);
    cout<<endl<<"line_relation="<<Line_relation(k,k2);
    Point g[4];
    g[0]=a;g[1]=b;g[2]=c;g[3]=d;
    cout<<endl<<"Point_in_polygon="<<Point_in_polygon(p,g,4);
    cout<<endl<<"Polygon_area="<<Polygon_area(g,4);
    cout<<endl<<endl;
	return 0;
}

(二)求半平面交

1.点向式求半平面交

#include <bits/stdc++.h>
using namespace std;
const double INF = 1e12;
const double pi = acos(-1.0);
const double eps = 1e-8;
int sgn(double x){
    if(fabs(x) < eps)  return 0;
    else return x<0?-1:1;
}
struct Point{
    double x,y;
    Point(){}
    Point(double x,double y):x(x),y(y){}
    Point operator + (Point B){return Point(x+B.x,y+B.y);}
    Point operator - (Point B){return Point(x-B.x,y-B.y);}
    Point operator * (double k){return Point(x*k,y*k);}
};
typedef Point Vector;
double Cross(Vector A,Vector B){return A.x*B.y - A.y*B.x;} //叉积
struct Line{
	Point p;
	Vector v;
	double ang;
	Line(){};
	Line(Point p,Vector v):p(p),v(v){ang=atan2(v.y,v.x);}
	bool operator < (Line &L){return ang<L.ang;}     //用于极角排序
};
//点p在线L左边,即点p在线L在外面:
bool OnLeft(Line L,Point p){return sgn(Cross(L.v,p-L.p))>0;}
Point Cross_point(Line a,Line b){    //两直线交点
    	Vector u=a.p-b.p;
		double t=Cross(b.v,u)/Cross(a.v,b.v);
		return a.p+a.v*t;
}
vector<Point> HPI(vector<Line> L){     //求半平面交,返回图多边形
	int n=L.size();
	sort(L.begin(),L.end());//将所有半平面按照极角排序。
	int l,r;       //指向双端队列的第一个和最后一个元素
	vector<Point> p(n);  //两个相邻半平面的交点
	vector<Line> q(n);   //双端队列
	vector<Point> ans;   //半平面交形成的凸包
	q[l=r=0]=L[0];
	for(int i=1;i<n;i++){
		//情况1:删除尾部的半平面
		while(l<r && !OnLeft(L[i], p[r-1])) r--;
		//情况2:删除首部的半平面:
		while(l<r && !OnLeft(L[i], p[l]))  l++;
		q[++r]=L[i];     //将当前的半平面加入双端队列尾部
		//极角相同的两个半平面,保留左边:
		if(fabs(Cross(q[r].v,q[r-1].v)) < eps){
			r--;
			if(OnLeft(q[r],L[i].p)) q[r]=L[i];
		}
		//计算队列尾部半平面交点:
		if(l<r) p[r-1]=Cross_point(q[r-1],q[r]);
	}
	//情况3:删除队列尾部的无用半平面
	while(l<r && !OnLeft(q[l],p[r-1])) r--;
	if(r-l<=1) return ans;   //空集
	p[r]=Cross_point(q[r],q[l]);  //计算队列首尾部的交点。
	for(int i=l;i<=r;i++)  ans.push_back(p[i]);   //复制。
	return ans;               //返回凸多边形
}
int main(){
     int T,n;
		cin>>T;
		while(T--){
			cin>>n;
		    vector<Line> L;
		   //加一个半平面F:反向y轴
         L.push_back(Line(Point(0,0),Vector(0,-1)));
			//加一个半平面E:y极大的向左的直线
		    L.push_back(Line(Point(0,INF),Vector(-1,0)));
  	    while(n--){
				double a,b;
				scanf("%lf%lf",&a,&b);
			 	L.push_back(Line(Point(0,a),Vector(1,b)));
		 }
		 vector<Point> ans=HPI(L);        //得到凸多边形
		 printf("%d\n",ans.size()-2);    //去掉人为加的两个点
	 }
     return 0;
}

2.两点式求半平面交一

const double INF = 1e12;
const double eps = 1e-8;
int n,m;
int sgn(double x){  //判断x是否等于0
    if(fabs(x) < eps)  return 0;
    else return x<0?-1:1;
}
struct Point{  //定义点和基本运算
    double x,y;
    Point(){}
    Point(double x,double y):x(x),y(y){}
    Point operator + (Point B){return Point(x+B.x,y+B.y);}
    Point operator - (Point B){return Point(x-B.x,y-B.y);}
    Point operator * (double k){return Point(x*k,y*k);}    //长度增大k倍
    Point operator / (double k){return Point(x/k,y/k);}    //长度缩小k倍
};
typedef Point Vector;   //定义向量
//点积
double Dot(Vector A,Vector B){return A.x*B.x + A.y*B.y;}
double Dist(Point A,Point B){return sqrt((A.x-B.x)*(A.x-B.x) + (A.y-B.y)*(A.y-B.y));}
double Cross(Vector A,Vector B){return A.x*B.y - A.y*B.x;}
double Area2(Point A, Point B, Point C){return Cross(B-A, C-A);} 
struct Line{
    Point p1,p2;//线上的两个点
    Line(){}
    Line(Point p1,Point p2):p1(p1),p2(p2){}
};
//得到极角角度
double get_angle(Line a) {
	return atan2(a.p2.y-a.p1.y,a.p2.x-a.p1.x);
}
//排序:极角小的排前面,极角相同时,最左边的排在最后面,以便去重
bool cmp(Line a, Line b) {
  double A =  get_angle(a), B = get_angle(b);
  if (fabs(A - B) < eps) return Area2(a.p1,a.p2,b.p2)<0;
  return A < B;
}
//点和直线关系:1 在左侧;2 在右侧;0 在直线上
int Point_line_relation(Point p,Line v){
    int c = sgn(Cross(p-v.p1,v.p2-v.p1));
    if(c < 0)return 1;       //1:p在v的左边
    if(c > 0)return 2;       //2:p在v的右边
    return 0;                //0:p在v上
}
//求两直线的交点 
Point Cross_point(Point a,Point b,Point c,Point d){ //Line1:ab,  Line2:cd
	double s1 = Cross(b-a,c-a);
	double s2 = Cross(b-a,d-a);  //叉积有正负
    return Point(c.x*s2-d.x*s1,c.y*s2-d.y*s1)/(s2-s1);
}
//两直线交点
Point Cross_point(Line a,Line b){    
    return Cross_point(a.p1,a.p2,b.p1,b.p2);
}
bool OnLeft(Line l,Point p){
	if(Point_line_relation(p,l) == 1)	return true;
	return false;
}
//求半平面交,返回图多边形
vector<Point> HPI(vector<Line> L){     
	int n=L.size();
	sort(L.begin(),L.end(),cmp);//将所有半平面按照极角排序。
	int l,r;       //指向双端队列的第一个和最后一个元素
	vector<Point> p(n);  //两个相邻半平面的交点
	vector<Point> ans;   //半平面交形成的凸包
	vector<Line> q(n);   //双端队列
	q[l=r=0]=L[0];
	for(int i=1;i<n;i++){
		//情况1:删除尾部的半平面
		while(l<r && !OnLeft(L[i], p[r-1])) r--;
		//情况2:删除首部的半平面:
		while(l<r && !OnLeft(L[i], p[l]))  l++;
		q[++r]=L[i];     //将当前的半平面加入双端队列尾部
		//极角相同的两个半平面,保留左边:
		if(fabs(Cross(q[r].p2-q[r].p1,q[r-1].p2-q[r-1].p1)) < eps){
			r--;
			if(OnLeft(q[r],L[i].p1)) q[r]=L[i];
		}
		//计算队列尾部半平面交点:
		if(l<r) p[r-1]=Cross_point(q[r-1],q[r]);
	}
	//情况3:删除队列尾部的无用半平面
	while(l<r && !OnLeft(q[l],p[r-1])) r--;
	if(r-l<=1) return ans;   //空集
	p[r]=Cross_point(q[r],q[l]);  //计算队列首尾部的交点。
	for(int i=l;i<=r;i++)  ans.push_back(p[i]);   //复制
	return ans;               //返回凸多边形
}
vector<Line> L;
vector<Point> ans;
int main(){
	int i,j,T;
	cin>>T;
	while(T--){
		int n;
		L.clear();
		cin>>n;
		for(i = 1;i<=n;i++){
			double p,v;
			scanf("%lf %lf",&p,&v);
			Line l(Point(0,p),Point(1,v+p));
			L.push_back(l);
		}
		//加入两个边界的直线
		L.push_back(Line(Point(0,0),Point(0,-1)));
		L.push_back(Line(Point(0,INF),Point(-1,INF)));
		vector<Point> ans = HPI(L);
		cout<<ans.size()-2<<endl;
	} 
	return 0;
}

3.两点式求半平面交模板二

typedef long long ll;
typedef long double LD;
typedef pair<int,int> PII;
const double inf = 1e12;
const int N = 100005;
const LD eps = 1e-18;
int n,m;
int sgn(double x){  //判断x是否等于0
    if(fabs(x) < eps)  return 0;
    else return x<0?-1:1;
}
int Dcmp(double x, double y){  //比较两个浮点数:0 相等;-1 小于;1 大于
    if(fabs(x - y) < eps) return 0;
    else return x<y ?-1:1;
}
struct Point{  //定义点和基本运算
    double x,y;
    Point(){}
    Point(double x,double y):x(x),y(y){}
    Point operator + (Point B){return Point(x+B.x,y+B.y);}
    Point operator - (Point B){return Point(x-B.x,y-B.y);}
    Point operator * (double k){return Point(x*k,y*k);}    //长度增大k倍
    Point operator / (double k){return Point(x/k,y/k);}    //长度缩小k倍
    bool operator == (Point B){return sgn(x-B.x)==0 && sgn(y-B.y)==0;}
    bool operator < (Point B){return sgn(x-B.x)<0 || (sgn(x-B.x)==0 && sgn(y-B.y)<0);} //用于凸包
};
typedef Point Vector;   //定义向量
double Cross(Vector A,Vector B){return A.x*B.y - A.y*B.x;}
double Area2(Point A, Point B, Point C){return Cross(B-A, C-A);} 
struct Line{
    Point p1,p2;//线上的两个点
    Line(){}
    Line(Point p1,Point p2):p1(p1),p2(p2){}
};
Line line[N];
int cnt;
int q[N];//定义双端队列
double get_angle(Line a) {
	return atan2(a.p2.y-a.p1.y,a.p2.x-a.p1.x);
}
//排序:极角小的排前面,极角相同时,最左边的排在最后面,以便去重
bool cmp(Line a, Line b) {
  double A =  get_angle(a), B = get_angle(b);
  if (fabs(A - B) < eps) return Area2(a.p1,a.p2,b.p2)<0;
  return A < B;
}
Point Cross_point(Point a,Point b,Point c,Point d){ //Line1:ab,  Line2:cd
	double s1 = Cross(b-a,c-a);
	double s2 = Cross(b-a,d-a);  //叉积有正负
    return Point(c.x*s2-d.x*s1,c.y*s2-d.y*s1)/(s2-s1);
}
//两直线交点
Point Cross_point(Line a,Line b){    
    return Cross_point(a.p1,a.p2,b.p1,b.p2);
}
// 判断bc的交点是否在a的右侧
bool on_right(Line a, Line b, Line c){
    Point o = Cross_point(b, c);
    //如果删除重合的点加上等号 
    return Area2(a.p1, a.p2, o) <= 0;
}
vector<Point> HPI(vector<Line> L){
	int cnt = L.size();
    sort(L.begin(),L.end(), cmp);
    int l = 0, r = -1;//头部和尾部 
    for (int i = 0; i < cnt; i ++ ){
        if (i && !Dcmp(get_angle(L[i - 1]), get_angle(L[i]))) continue;
        while (l + 1 <= r && on_right(L[i], L[q[r - 1]], L[q[r]])) r -- ;
        while (l + 1 <= r && on_right(L[i], L[q[l]], L[q[l + 1]])) l ++ ;
        q[ ++ r] = i;
    }
    while (l + 1 <= r && on_right(L[q[l]], L[q[r - 1]], L[q[r]])) r -- ;
    while (l + 1 <= r && on_right(L[q[r]], L[q[l]], L[q[l + 1]])) l ++ ;
    int k = 0;
    vector<Point> ans;
    if(r - l <= 1)	return ans;//返回空集 
    q[++r] = q[l];//将头部的直线加入尾部 
    for(int i = l;i<r;i++){
    	ans.push_back(Cross_point(L[q[i]],L[q[i+1]]));
	}
    return ans;
}
double Polygon_area(vector<Point> p){ //Point *p表示多边形。
    double area = 0;
    int n = p.size();
    for(int i = 0;i < n;i++)
        area += Cross(p[i],p[(i+1)%n]);
    return area/2;    //面积有正负,不能简单地取绝对值
}

vector<Line> L;
vector<Point> ans;
Point pg[N];
int main(){
	int i,j,T;
	cin>>T;
	while(T--){
		int n;
		L.clear();
		cin>>n;
		for(i = 1;i<=n;i++){
			double p,v;
			scanf("%lf %lf",&p,&v);
			Line l(Point(0,p),Point(1,v+p));
			L.push_back(l);
		}
		//加入两个边界的直线
		L.push_back(Line(Point(0,0),Point(0,-1)));
		L.push_back(Line(Point(0,inf),Point(-1,inf)));
		vector<Point> ans = HPI(L);
		cout<<ans.size()-2<<endl;
	} 
	return 0;
}

(三)模拟退火求最小球覆盖

#include<bits/stdc++.h>
using namespace std;
const int MAXN=105;
const double EPS=1e-8;
struct Point{
    double x,y,z;
}Dots[MAXN];
int n;
double Distance(Point a,Point b){
    return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)+(a.z-b.z)*(a.z-b.z));
}
double Solve(){
    double Step=200,ans=1e9,mt;
    Point z=Point{0,0,0};
    int s=0;
    while(Step>EPS){
        for(int i=1;i<=n;i++)
            if(Distance(z,Dots[s])<Distance(z,Dots[i]))
                s=i;
        mt=Distance(z,Dots[s]);
        ans=min(ans,mt);
        z.x+=(Dots[s].x-z.x)/mt*Step;
        z.y+=(Dots[s].y-z.y)/mt*Step;
        z.z+=(Dots[s].z-z.z)/mt*Step;
        Step*=0.9999;
    }
    return ans;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        double x,y,z;
        scanf("%lf %lf %lf",&x,&y,&z);
        Dots[i]=Point{x,y,z};
    }
    printf("%.7f\n",Solve());
}
很多人会对Step、EPS、0.9999这几个参数很难理解,但这就是模拟退火的精髓。
  • 25
    点赞
  • 73
    收藏
    觉得还不错? 一键收藏
  • 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<O(N), O(1)> 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
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、付费专栏及课程。

余额充值