个人ACM模板总结

博主分享ACM算法模板,涵盖图论(如Dijkstra、Prim算法)、数论(质数判定、约数计算)、数据结构(堆、并查集)、字符串(Kmp算法)等多方面内容,还包括STL使用、动态规划、排序、二分等算法,以及Java高精度和计算几何相关模板。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

大二下开学接触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这几个参数很难理解,但这就是模拟退火的精髓。
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
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值