文章目录

  • 数据结构
    • 并查集
    • 树状数组
      • 二维单点修改,区间查询
      • 二维区间修改,单点查询
        • 二维区间修改,区间查询
    • 线段树
      • 单点修改,区间查询
      • 区间更新、区间查询
    • 主席树(区间第k小数模板)
    • 单调栈
    • 单调队列
    • Trie树
    • 01Trie树
  • 图论
    • 最短路
      • spfa最短路
      • 迪杰斯特拉(堆优化)
      • 迪杰斯特拉(堆优化+链式前向星)
    • 最短路径计数
    • 最小生成树
      • kruskal
      • prim
    • 次小生成树
      • 非严格次小生成树
        • prime+dp O(V^2)
        • 方法二:倍增+kruskal O(MlogN)
      • 严格次小生成树
        • 倍增:O(mlogm)
    • LCA
      • 倍增模板:O(nlogn)
      • tarjan模板:0(n+m)
    • 二分图
      • 匈牙利算法:O(n×m)O(n\times m)O(n×m)
      • KM()算法,求二分图的带权最大匹配,O(n^3)
    • 差分约束
    • 求树的重心
    • 无向图割点和割边
      • Tarjan 算法求割点
      • Tarjan 算法求桥
      • Tarjan算法之割边
        • 定义
        • 算法
        • 例题:
        • code
    • 树的直径
  • 数论
    • 数论常见公式
      • 卡特兰数
      • 错排公式
      • 常用前n项和公式
    • GCD
    • 拓展GCD
    • 素数判断
      • 朴素素数判断
      • 埃式筛
      • 欧拉筛
    • 快速幂&龟速乘
    • 组合数模板
    • SG函数
    • 高斯消元
      • 不带模数的求行列式
      • 带模数的求行列式
    • Miller Rabin算法
  • 字符串
    • Manacher模板
    • KMP模板
    • 字符串hash(自然溢出)
    • AC自动机
  • DP动态规划
    • 背包模板
      • 01背包
      • 完全背包
      • 多重背包
      • 分组背包
      • 单调队列优化的背包:
      • 超大背包问题
    • 数位DP
      • 例一
      • 例二
    • LIS(最长上身子序列)nlogn
  • STL
    • vector
    • priority_queue优先队列(默认为大根堆)
    • set(二叉搜索树)
    • map
    • pair
  • algorithm常用函数
    • unique()去重函数
    • sort()函数
    • 求第k小数
    • 二分函数:
  • 计算几何
    • 计算几何基本模板
    • 半平面交
      • 点向式求半平面交
      • 两点式求半平面交一
      • 两点式求半平面交模板二
    • N圆面积并&&交
    • 凸包&判凸包相交
  • 其他
    • 快读快输模板
    • _int128模板
    • 二分模板
    • 排序
      • 快速排序
      • 归并排序
      • 归并排序求逆序对
    • JAVA高精度
    • C++高精度
    • 坐标离散化
      • 例一
        • 步骤
        • code

本篇文章主要是Mangata平时写代码所用到的代码模板库,如有不对请在评论区指出
 
先放一个我的 常数优化的博客: 传送门
 
再放一个我的 代码格式博客: 传送门

数据结构

并查集

并查集是一种集合数据结构,通过并查集我们可以快速查询两个元素是否是一个集合,下面是Mangata常用的板子

/*作者:Mangata路径压缩并查集
*/
#include<cstdio>const int N = 10005;//节点数
int fa[N],n;void init(int len) {//初始化,先让每个位置的父节点等于自身 for(int i=0; i<=n; ++i)fa[i] = i;
}int find(int x) {//查找x的祖先节点 int t = x;//路径压缩while(t != fa[t]) t = fa[t];while(x != fa[x]) {int temp = fa[x];fa[x] = t;x = temp;}return x;
}void merge(int a,int b) {//将x和y合并 a=find(a),b=find(b);if(a != b) {fa[b] = a;n--;}
}int main()
{int t,m,a,b;scanf("%d",&t);while(t--) {scanf("%d%d",&n,&m);init(n);for(int i = 1; i <= m; ++i) {scanf("%d%d",&a,&b);merge(a,b);}printf("%d\n",n);//输出不同类别的总数目}return 0;
}

树状数组

二维单点修改,区间查询

例题:传送门

Code:

#include<bits/stdc++.h>
using namespace std;
#define ll long longconst int N = 5e3;ll tree[N][N<<2];
int n,m;int lowbit(int x) {return -x & x;
}inline void updata(int x,int y,int k) {while(x <= n) {int temp = y;while(y <= m) {tree[x][y] += k;y += lowbit(y);}x += lowbit(x);y = temp;}
}inline ll get(int x,int y) {ll ans = 0;while(x) {int temp = y;while(y) {ans += tree[x][y];y -= lowbit(y);}x -= lowbit(x);y = temp;}return ans;
}int main()
{scanf("%d%d",&n,&m);int a,b,c,d,op;while(~scanf("%d",&op)) {if(op == 1) {scanf("%d%d%d",&a,&b,&c);updata(a,b,c);}else {scanf("%d%d%d%d",&a,&b,&c,&d);printf("%lld\n",get(c,d)-get(c,b-1)-get(a-1,d) + get(a-1,b-1));}}return 0;}

二维区间修改,单点查询

利用二维差分进行更新,更新和查询复杂度复杂度log22log^2_2log22​

#include<bits/stdc++.h>using namespace std;
#define ll long long const int N = 5e3+10;
ll tree[N][N];
ll t,n,m;ll lowbit(ll x) {return -x & x;
}void add(ll x,ll y,ll k) {//单点更新 while(x <= n) {ll temp = y;while(y <= m) {tree[x][y] += k;y += lowbit(y);}x += lowbit(x);y = temp;}
}void updata(ll x1,ll y1,ll x2,ll y2,ll k) {//矩阵更新 add(x1,y1,k);add(x1,y2 + 1,-k);add(x2 + 1,y1,-k);add(x2 + 1,y2 + 1,k);
}ll query(ll x,ll y) {//单点查询ll ans = 0;while(x) {ll temp = y;while(y) {ans += tree[x][y];y -= lowbit(y);}x -= lowbit(x);y = temp;}return ans;
}int main()
{ll x1,x2,y1,y2,k;memset(tree,0,sizeof tree);scanf("%lld%lld",&n,&m);//n X m的矩阵 int op;while(~scanf("%d",&op)) {if(op == 1) {scanf("%lld%lld%lld%lld%lld",&x1,&y1,&x2,&y2,&k);updata(x1,y1,x2,y2,k);}else {scanf("%lld%lld",&x1,&y1);ll kk = query(x1,y1);printf("%lld\n",kk);}}return 0;
}

二维区间修改,区间查询

#include<bits/stdc++.h>
using namespace std;
#define ll long longconst int N = 3e3;ll n,m;
ll tree[N][N][4];ll read()
{ll X=0; bool flag=1; char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') flag=0; ch=getchar();}while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();}if(flag) return X;return ~(X-1);
}ll write(ll X)
{if(X<0) {putchar('-'); X=~(X-1);}int s[65],top=0;while(X) {s[++top]=X%10; X/=10;}if(!top) s[++top]=0;while(top) putchar(s[top--]+'0');
}inline ll lowbit(ll x) {return -x & x;
}void add(ll x,ll y,ll k) {for(ll i = x;i <= n; i += lowbit(i)) {for(ll j = y;j <= m; j += lowbit(j)) {tree[i][j][0] += k;tree[i][j][1] += k * x;tree[i][j][2] += k * y;tree[i][j][3] += k * x * y;}}
}void updata(ll x1,ll y1,ll x2,ll y2,ll k) {add(x1,y1,k);add(x1,y2 + 1,-k);add(x2 + 1,y1,-k);add(x2 + 1,y2 + 1,k);
}ll ask(ll x,ll y) {ll ans = 0;for(ll i = x;i; i -= lowbit(i)) {for(ll j = y;j; j -= lowbit(j)) {ans += (x + 1) * (y + 1) * tree[i][j][0] -  (y + 1) * tree[i][j][1] -  (x + 1) * tree[i][j][2] +            tree[i][j][3];}}return ans;
}ll query(ll x1,ll y1,ll x2,ll y2) {return ask(x1-1,y1-1) - ask(x1 - 1,y2) - ask(x2,y1 - 1) + ask(x2,y2);
}int main()
{n = read();m = read();int op;ll x1,x2,y1,y2,k;while(~scanf("%d",&op)) {if(op == 1) {scanf("%lld%lld%lld%lld%lld",&x1,&y1,&x2,&y2,&k);updata(x1,y1,x2,y2,k);}else {scanf("%lld%lld%lld%lld",&x1,&y1,&x2,&y2);ll kk = query(x1,y1,x2,y2);printf("%lld\n",kk);}}return 0;
}

线段树

单点修改,区间查询

例题:HDU1754

#include<bits/stdc++.h>
using namespace std;const int INF = 0x3f3f3f3f;
const int N = 2000005;
int n,m;
int a[N],tree[N << 2];void push_up(int k) {tree[k] = max(tree[k<<1],tree[k<<1|1]);
}
void build(int k, int l,int r) {if(l == r) {tree[k] = a[l];}else {int mid = l + ((r-l)>>1);build(k<<1,l,mid);build(k<<1|1,mid+1,r);push_up(k);}
}void updata(int p,int v,int l,int r,int k) {if(l == r) {a[p] += v, tree[k] += v;}else {int mid = l + ((r-l)>>1);if(p <= mid) {updata(p,v,l,mid,k<<1);}else {updata(p,v,mid+1,r,k<<1|1);}push_up(k);}
}int query(int L, int R,int l,int r,int k) {if(L <= l && R >= r) {return tree[k];}else {int ans = -INF;int mid = l+r >>1;if(L <= mid) {ans = max(ans,query(L,R,l,mid,k<<1));}if(R > mid) {ans = max(ans,query(L,R,mid+1,r,k<<1|1));}return ans;}
}
int main()
{while(~scanf("%d%d",&n,&m)) {for(int i = 1;i <= n; ++i) {scanf("%d",&a[i]);}build(1,1,n);char op;int l,r;while(m--) {cin>>op;if(op == 'Q') {scanf("%d%d",&l,&r);printf("%d\n",query(l,r,1,n,1));}else if(op == 'U'){scanf("%d%d",&l,&r);updata(l,r-a[l],1,n,1);}}}return 0;
}

区间更新、区间查询

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e6+10;ll tree[N<<2],tag[N<<2];
ll a[N];inline ll ls(ll p) {return p<<1;}
inline ll rs(ll p) {return p<<1|1;}void push_up_min(ll p) {//向上更新操作 tree[p] = min(tree[ls(p)],tree[rs(p)]);
}void build(ll p,ll l,ll r) {//建树操作 if(l == r) {tree[p] = a[l];return;}ll mid = l + r >> 1;build(ls(p),l,mid);build(rs(p),mid+1,r);push_up_min(p);
}inline void push_down(ll p,ll l,ll r) {//向下更新操作if(tag[p]) {tag[ls(p)] += tag[p];tag[rs(p)] += tag[p];tree[ls(p)] += tag[p];tree[rs(p)] += tag[p];tag[p] = 0;}//更新两个儿子节点的tag和tree值
}void updata(ll L ,ll R ,ll l ,ll r ,ll p ,ll k) {//[L,R]是我们想要修改的区间,if(L <= l && R >= r) {               //[l,r]是我们目前搜索到的区间tree[p] += k;tag[p] += k;return;}push_down(p,l,r);//回溯前往下传递更新ll mid = l + r >> 1;if(L <= mid) updata(L,R,l,mid,ls(p),k);//如果我们想修改的区间比当前的中间节点小or等于那么就查询 if(R >  mid) updata(L,R,mid+1,r,rs(p),k);//如果我们想修改的区间比当前中间节点大那么就查询 push_up_min(p);//回溯后往上传递更新
}ll query(ll L, ll R,ll l,ll r,ll p) {//[L,R]是我们要查询的区间,[l,r]是当前节点存储的区间 ll res = 0x3f3f3f3f3f3f3f3f;if(L <= l && R >= r) return tree[p];ll mid = l + r >> 1;push_down(p,l,r);if(L <= mid)res = min(res,query(L,R,l,mid,ls(p)));if(R > mid)res = min(res,query(L,R,mid+1,r,rs(p)));return res;
}
int n,m;int main()
{scanf("%d%d",&n,&m);for(int i = 1;i <= n; ++i) {scanf("%lld",&a[i]);}build(1,1,n);int q;ll l,r,k;int fg = 0;for(int i = 1;i <= m; ++i) {scanf("%lld%lld%lld",&k,&l,&r);if(fg) continue;if(query(l,r,1,n,1) >= k)updata(l,r,1,n,1,-k);else fg = i;}if(fg) {printf("-1\n%d\n",fg);}else puts("0");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都为0int 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);elsetr[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);elsereturn 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;
}

单调栈

//单调栈:输出左边第一个比它小的数,如果没有输出-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;
}

Trie树

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;const int N = 5e5+10;//请注意这里的数组大小要开字符串的个数*字符串的长度这么大
int n,m;
struct trie {int nextt[N][26],cnt;//nextt[i][j]存储的是第i个字符下一个字符j的位置信息int exit[N];//exit[i]表示以值为i结束的这个字符串是否存在void insert(char *s,int l) {int p = 0;//这个表示的是当前的位置指针for(int i = 0;i < l; ++i) {int c = s[i] - 'a';if(!nextt[p][c]) nextt[p][c] = ++cnt;//如果下面一层是空的,那么就更新下一层的值p = nextt[p][c];//更新当前的p指针}exit[p] = 1;}void find(char *s,int l) {int p = 0;for(int i = 0;i < l; ++i) {int c = s[i] - 'a';if(!nextt[p][c]) break;//这里表示没找到,那么我们直接退出即可p = nextt[p][c];}if(exit[p] == 1) {puts("OK");exit[p] = 2;}else if(exit[p] == 2) {puts("REPEAT");}else if(exit[p] == 0) {puts("WRONG");}}
}T;char S[N];int main()
{scanf("%d%d",&n,&m);for(int i = 0;i < n; ++i) {scanf(" %s",S);T.insert(S,strlen(S));}scanf("%d",&m);for(int i = 0;i < m; ++i) {scanf("%s",S);T.find(S,strlen(S));}return 0;
}

01Trie树

#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
#define ll long long
const int N = 1e5+10;
int n,m;
struct Trie {ll nextt[N*32][2];ll exit[N*32];ll cnt = 0;void init() {memset(nextt,0,sizeof nextt);memset(exit,0,sizeof exit);cnt = 0;}void insert(ll k) {ll p = 0;for(int i = 32;i >= 0; --i) {ll c = (k>>i) & 1;if(!nextt[p][c]) nextt[p][c] = ++cnt;p = nextt[p][c];}exit[p] = k;}ll find(ll k) {ll p = 0;for(int i = 32;i >= 0; --i) {ll c = (k>>i) & 1;if(nextt[p][c^1]) p = nextt[p][c^1];else p = nextt[p][c];}return exit[p];}
}T;int main()
{int t; scanf("%d",&t);for(int j = 1;j <= t;++j) {scanf("%d%d",&n,&m);ll k;T.init();for(int i = 0;i < n; ++i) {scanf("%lld",&k);T.insert(k);}printf("Case #%d:\n",j);while(m--) {scanf("%lld",&k);printf("%lld\n",T.find(k));}}return 0;
}

图论

最短路

spfa最短路

#include <iostream>
#include <string.h>
#include <stdio.h>
#include <algorithm>
#include <queue>
#include <vector>
#define ll long long
#define inf 0x3f3f3f3f
#define pii pair<int, int>
const int mod = 1e9+7;
const int maxn = 2e5+7;
using namespace std;
struct node {int to,w,next;} edge[maxn];
int head[maxn], cnt;
int dis[maxn], vis[maxn];
int n, m, s, t;
struct Spfa
{void init(){memset(head,-1,sizeof(head));memset(dis,0x3f3f3f3f,sizeof(dis));memset(vis,0,sizeof(vis));cnt = 0;}void add(int u,int v,int w){edge[cnt].to = v;edge[cnt].w = w;edge[cnt].next = head[u];head[u] = cnt ++;}void spfa(){dis[s] = 0; vis[s] = 1;queue <int> Q; Q.push(s);while(!Q.empty()){int now = Q.front();Q.pop(); vis[now] = 0;    //从队列中弹出now , vis[now] 标记为未访问for(int i = head[now]; i != -1; i = edge[i].next){int v = edge[i].to;if(dis[v] < dis[now] + edge[i].w){dis[v] = dis[now] + edge[i].w;if(vis[v]) continue;    //如果访问过了(也就是 已经在队列中),就不用再pushvis[v] = 1; Q.push(v);}}}}
}sp;int main()
{while(~scanf("%d%d",&n,&m) && n+m){sp.init();for(int i = 0; i < m; i++){int u, v, w;scanf("%d%d%d",&u, &v, &w);sp.add(u, v, w);sp.add(v, u, w);}s = 1, t = n; //s起点,t终点sp.spfa();printf("%d\n", dis[t]);}
}

迪杰斯特拉(堆优化)

#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
#include<vector>
#define N 100005
#define INF 2147483647
using namespace std;
typedef pair<int,int> P;
struct edge{int cost,to;
};
vector<edge> G[N];
int d[N];
int n , m ,s;
void dijikstra(){priority_queue<P, vector<P>, greater<P> >que;for(int i=0;i<=n;++i){d[i]=INF;}d[s]=0;que.push({0,s});while(!que.empty()){P p=que.top();que.pop();int v=p.second;if(d[v]<p.first)continue;for(int i=0;i<G[v].size();++i){edge e=G[v][i];if(d[e.to]>d[v]+e.cost){d[e.to]=d[v]+e.cost;que.push({d[e.to],e.to});}}}
}
int main(){int u,v,w;scanf("%d%d%d",&n,&m,&s);for(int i=0;i<m;++i){scanf("%d%d%d",&u,&v,&w);G[u].push_back({w,v});}dijikstra();for(int i=1;i<n;++i){printf("%d ",d[i]);}printf("%d\n",d[n]);return 0;
}

迪杰斯特拉(堆优化+链式前向星)

#include<cstdio>
#include<cstring>
#include<queue>//
using namespace std;
const int N=2e5+5;//数据范围
struct edge{//存储边int u,v,w,next;//u为起点,v为终点,w为权值,next为前继
};
edge e[N];
int head[N],dis[N],n,m,s,cnt;//head为链中最上面的,dis表示当前答案,n为点数,m为边数,s为起点,cnt记录当前边的数量
bool vis[N];//vis表示这个点有没有走过
struct node{int w,to;//w表示累加的权值,to表示到的地方bool operator <(const node &x)const{//重载“<”号return w>x.w;}
};
priority_queue<node>q;//优先队列(堆优化)
void add(int u,int v,int w){++cnt;//增加边的数量e[cnt].u=u;//存起点e[cnt].v=v;//存终点e[cnt].w=w;//存权值e[cnt].next=head[u];//存前继head[u]=cnt;//更新链最上面的序号
}//链式前向星(加边)
void Dijkstra(){memset(dis,0x3f3f3f3f,sizeof(dis));//初始化,为dis数组附一个极大值,方便后面的计算dis[s]=0;//起点到自己距离为0q.push(node{0,s});//压入队列while(!q.empty()){//队列不为空node x=q.top();//取出队列第一个元素q.pop();//弹出int u=x.to;//求出起点if(vis[u]) continue;//已去过就不去了vis[u]=true;//标记已去过for(int i=head[u];i;i=e[i].next){int v=e[i].v;//枚举终点if(dis[v]>dis[u]+e[i].w){//若中转后更优,就转dis[v]=dis[u]+e[i].w;//更新q.push(node{dis[v],v});//压入队列}}}
}
int main(){int u,v,w = 1;s = 1;scanf("%d%d",&n,&m);//输入for(int i=1;i<=m;++i){scanf("%d%d",&u,&v);add(u,v,w);add(v,u,w);}Dijkstra();//DJprintf("%d\n",dis[n]-1);//输出1-n的最短路return 0;
}

最短路径计数

dijkstra拆点最短次短路计数

(只对最短路径计数时,不需要拆点,正常bfs或者dijkstra就可以了)
(注意,一定要先更新次短路点的信息,因为最短路没有更新的时候,次最短路可能更新;而次最短路更新的时候,最短路一定会更新;)#define x first
#define y second
typedef pair<pair<int,int>,int> PII;
int n, m, S, F;
const int N=1010,M=20010;
int h[N], e[M], w[M], ne[M], idx;
int dis[N][2], cnt[N][2];//0表示最短路状态,1表示次短路状态
bool st[N][2];
void add(int a, int b, int c)
{e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
int dijkstra(int root)
{memset(dis,0x3f,sizeof dis);memset(st,false,sizeof st);memset(cnt,0,sizeof cnt);dis[root][0]=0,cnt[root][0]=1;priority_queue<PII,vector<PII>,greater<PII> > heap;heap.push({{0,0},root});//依次存放最短距离,状态类型,点的id,注意,第一个一定要放最短距离while(!heap.empty()){auto t=heap.top();heap.pop();int ver=t.y,type=t.x.y,distance=t.x.x,res=cnt[ver][type];if(st[ver][type]) continue;st[ver][type] = true;for(int i=h[ver];i!=-1;i=ne[i]){int j=e[i];if(dis[j][0]>distance+w[i]){dis[j][1]=dis[j][0],cnt[j][1]=cnt[j][0];heap.push({{dis[j][1],1},j});dis[j][0]=distance+w[i],cnt[j][0]=res;heap.push({{dis[j][0],0},j});}else if(dis[j][0]==distance + w[i]) cnt[j][0]+=res;else if(dis[j][1]>distance+w[i]){dis[j][1]=distance+w[i];cnt[j][1]=res;heap.push({{dis[j][1],1},j});}else if(dis[j][1]==distance + w[i]){cnt[j][1]+=res;}}}
//dijkstra处理之后,要对终点的状态进行判断int ans = cnt[F][0];if (dis[F][0] + 1 == dis[F][1]) ans += cnt[F][1];return ans;
}

最小生成树

kruskal

最小生成数基于并查集的写法

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 205struct node{int from,to;long long cost;
}E[N*N];int fa[N],n,m;bool cmp(node a,node b){return a.cost<b.cost;
}int find(int x){int k = x;while(k != fa[k])k = fa[k]; while(x != fa[x]) {int t =fa[x];fa[x] = k;x = t;}return x;
}void init(){for(int i = 1; i <= n; ++i)fa[i] = i;
}int same(int x,int y){return find(x) == find(y);
}void merge(int a,int b){a = find(a);b = find(b);if(a != b)fa[b] = a;
}
long long kruskal(){long long ans = 0;sort(E+1,E+m+1,cmp);for(int i = 1; i <= m; ++i){if(same(E[i].from,E[i].to))continue;merge(E[i].from,E[i].to);ans += E[i].cost;}return ans;//返回的是最小生成树的代价
}
int main(){int u,v,w;while(~scanf("%d%d",&m,&n)&&m){init();for(int i = 1; i <= m; ++i){scanf("%d%d%d",&u,&v,&w);E[i].from = u,E[i].to = v,E[i].cost = w;}long long k = kruskal();int loc = find(1);for(int i = 2; i <= n; ++i)if(find(i) != loc){k = -1;break;}if(k == -1)puts("?");elseprintf("%lld\n",k);}return 0;
}

prim

最小生成树基于prim的写法

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
#define P pair<int, int>
#define INF 0x3f3f3f3fconst int N = 1005;
int mp[N][N];
bool vis[N];
int dis[N];
int n,m;//返回的是一个生成树每条边的和
int prim(int s) {for(int i = 1; i <= n ; ++i) {dis[i] = INF;vis[i] = false;}dis[s] = 0;int ans = 0;while(true) {int v = -1;for(int u = 1; u <= n; ++u) {if(!vis[u] && (v == -1 || dis[u] < dis[v])) {v = u;}}if(v == -1)break;vis[v] = true;ans += dis[v];//松弛操作 更新最短路径 for(int u = 1; u <= n; ++u) {dis[u] = min(dis[u], mp[u][v]);}}return ans;
}int main()
{int u,v,cost;while(~scanf("%d%d",&m, &n) && m) {memset(mp,INF,sizeof mp);for(int i = 0; i < m; ++i) {scanf("%d%d%d",&u, &v, &cost);if(mp[u][v] > cost)mp[u][v] = mp[v][u] = cost;}int ans = prim(1);if(ans < INF) {printf("%d\n",ans);} else {puts("?");}}return 0;
}

次小生成树

非严格次小生成树

prime+dp O(V^2)


#include <bits/stdc++.h>using namespace std;
#define ll long long
#define mem(a, b) memset(a,b,sizeof(a))
#define INF 0x3f3f3f3f
#define DBG printf("this is a input\n")
#define fi first
#define se second
#define mk(a, b) make_pair(a,b)
#define p_queue priority_queuell gcd(ll a, ll b) {return b == 0 ? a : gcd(b, a % b);
}ll lcm(ll a, ll b) {return a / gcd(a, b) * b;
}
int T, n;
double edge[1005][1005], dp[1005][1005], dis[1005];
bool vis[1005], used[1005][1005];
int pre[1005];
struct node
{double x, y, value;
}point[1005];
double prime()
{double mst = 0;mem(vis, false);mem(dp, 0);mem(used, false);vis[1] = true;dis[1] = 0;//以根节点初始化for (int i = 2; i <= n; i++) {dis[i] = edge[1][i];pre[i] = 1;//记录可以到达点的父亲节点}for (int i = 2; i <= n; i++){int index = -1;double maxn = INF;for(int j = 1 ; j <= n ; j ++){if(!vis[j] && maxn > dis[j]){index = j;maxn = dis[j];}}mst += maxn;vis[index] = true;used[pre[index]][index] = used[index][pre[index]] = true; //记录已使用边for(int j = 1 ; j <= n ; j ++){//dp维护已走过点,两点间的最大边//1 -> 2 -> 3 -> 4 , index = 4//dis[index]代表3->4 dp[j][pre[index]]代表 1->3 , 2->3路径中边的最大值if(vis[j] && j != index)dp[index][j] = dp[j][index] = max(dp[j][pre[index]],dis[index]);if(!vis[j] && dis[j] > edge[index][j]){dis[j] = edge[index][j];pre[j] = index;}}}return mst;
}
int main(void)
{scanf("%d",&T);while(T --){mem(edge,0);scanf("%d",&n);for(int i = 1 ; i <= n ; i ++)scanf("%lf %lf %lf",&point[i].x,&point[i].y,&point[i].value);for(int i = 1 ; i <= n ; i ++)for(int j = 1 ; j <= n ; j ++)if(i != j)edge[i][j] = sqrt( (point[i].x-point[j].x)*(point[i].x-point[j].x) + (point[i].y-point[j].y)*(point[i].y-point[j].y));double mst = prime();double ans = -1;for(int i = 1 ; i <= n ; i ++){for(int j = 1;  j <= n ; j ++){if(i != j){if(used[i][j])ans = max(ans,(point[i].value+point[j].value)/(mst-edge[i][j]));elseans = max(ans,(point[i].value+point[j].value)/(mst-dp[i][j]));}}}printf("%.2f\n",ans);}
}

方法二:倍增+kruskal O(MlogN)

#include <bits/stdc++.h>using namespace std;
#define ll long long
#define mem(a, b) memset(a,b,sizeof(a))
#define INF 0x3f3f3f3f
#define DBG printf("this is a input\n")
#define fi first
#define se second
#define mk(a, b) make_pair(a,b)
#define p_queue priority_queuell gcd(ll a, ll b) {return b == 0 ? a : gcd(b, a % b);
}ll lcm(ll a, ll b) {return a / gcd(a, b) * b;
}
int T;
int n , m, cnt;
int head[1505],fa[1505],vis[1505],used[1505],deep[1505];
int maxn[1505][30] , bz[1505][30];
struct node
{int f, t, next, w;bool operator < (const node& no) const{return no.w > w;}
}edge[25000],b_edge[25000];
int findroot(int x)
{if(fa[x] == x)return x;return fa[x] = findroot(fa[x]);
}
bool merge(int x, int y)
{int fax = findroot(x);int fay = findroot(y);if(fax != fay) {fa[fax] = fay;return true;}return false;
}
void add(int f, int t , int w)
{b_edge[cnt].t = t;b_edge[cnt].w = w;b_edge[cnt].next = head[f];head[f] = cnt ++;
}
void Init()
{cnt = 0;mem(head,-1);mem(used,0);mem(vis,0);for(int i = 1 ; i <= n ; i ++)fa[i] = i;
}
int kruskal()
{int tot = 0;int mst = 0;for(int i = 1 ; i <= m ; i ++){if(tot == n-1)break;int u = edge[i].f, v = edge[i].t, w = edge[i].w;if(merge(u,v)){tot ++;mst += w;used[i] = 1;add(u,v,w);add(v,u,w);}}if(tot == n-1)return mst;return -1;
}
void bfs()
{queue <int> q;q.push(1);vis[1] = 1;deep[1] = 0;while(!q.empty()){int no = q.front();q.pop();for(int i = head[no] ; i != -1 ; i = b_edge[i].next){int v = b_edge[i].t, w = b_edge[i].w;if(!vis[v]){vis[v] = 1;deep[v] = deep[no] + 1;bz[v][0] = no;maxn[v][0] = w;q.push(v);}}}
}
void Deal()
{for(int j = 1 ; j <= 20 ; j ++){for(int i = 1 ; i <= n ; i ++){bz[i][j] = bz[bz[i][j-1]][j-1];maxn[i][j] = max(maxn[i][j-1],maxn[bz[i][j-1]][j-1]);}}
}
int LCA(int x, int y)
{if(deep[x] < deep[y])swap(x,y);for(int i = 20 ; i >= 0 ; i --)if(deep[bz[x][i]] >= deep[y])x = bz[x][i];if(x == y) return x;for(int i = 20 ; i >= 0 ; i --){if(bz[x][i] ^ bz[y][i])x = bz[x][i] , y = bz[y][i];}return bz[x][0];
}
int path_maxn(int u , int v)
{int ans = -INF;for(int i = 20 ; i >= 0 ; i --){if(deep[bz[u][i]] >= deep[v]) {ans = max(ans, maxn[u][i]);u = bz[u][i];}}return ans;
}
int main(void)
{cin>>T;for(int t = 1 ; t <= T ; t ++){cin>>n>>m;Init();for(int i = 1 ; i <= m ; i ++)cin>>edge[i].f>>edge[i].t>>edge[i].w;sort(edge+1,edge+1+m);int mst = kruskal();if(mst == -1){printf("Case #%d : No way\n",t);continue;}bfs();Deal();int se_mst = INF;for(int i = 1 ; i <= m ; i ++){if(!used[i]){int u = edge[i].f;int v = edge[i].t;int w = edge[i].w;int lca = LCA(u,v);int ans_ma = path_maxn(u,lca);int ans_mi = path_maxn(v,lca);se_mst = min(se_mst,mst-max(ans_ma,ans_mi)+w);}}if(se_mst != INF)printf("Case #%d : %d\n",t,se_mst);elseprintf("Case #%d : No second way\n",t);}
}

严格次小生成树

倍增:O(mlogm)

#include <algorithm>
#include <iostream>const int INF = 0x3fffffff;
const long long INF64 = 0x3fffffffffffffffLL;struct Edge {int u, v, val;bool operator<(const Edge &other) const { return val < other.val; }
};Edge e[300010];
bool used[300010];int n, m;
long long sum;class Tr {private:struct Edge {int to, nxt, val;} e[600010];int cnt, head[100010];int pnt[100010][22];int dpth[100010];// 到祖先的路径上边权最大的边int maxx[100010][22];// 到祖先的路径上边权次大的边,若不存在则为 -INFint minn[100010][22];public:void addedge(int u, int v, int val) {e[++cnt] = (Edge){v, head[u], val};head[u] = cnt;}void insedge(int u, int v, int val) {addedge(u, v, val);addedge(v, u, val);}void dfs(int now, int fa) {dpth[now] = dpth[fa] + 1;pnt[now][0] = fa;minn[now][0] = -INF;for (int i = 1; (1 << i) <= dpth[now]; i++) {pnt[now][i] = pnt[pnt[now][i - 1]][i - 1];int kk[4] = {maxx[now][i - 1], maxx[pnt[now][i - 1]][i - 1],minn[now][i - 1], minn[pnt[now][i - 1]][i - 1]};// 从四个值中取得最大值std::sort(kk, kk + 4);maxx[now][i] = kk[3];// 取得严格次大值int ptr = 2;while (ptr >= 0 && kk[ptr] == kk[3]) ptr--;minn[now][i] = (ptr == -1 ? -INF : kk[ptr]);}for (int i = head[now]; i; i = e[i].nxt) {if (e[i].to != fa) {maxx[e[i].to][0] = e[i].val;dfs(e[i].to, now);}}}int lca(int a, int b) {if (dpth[a] < dpth[b]) std::swap(a, b);for (int i = 21; i >= 0; i--)if (dpth[pnt[a][i]] >= dpth[b]) a = pnt[a][i];if (a == b) return a;for (int i = 21; i >= 0; i--) {if (pnt[a][i] != pnt[b][i]) {a = pnt[a][i];b = pnt[b][i];}}return pnt[a][0];}int query(int a, int b, int val) {int res = -INF;for (int i = 21; i >= 0; i--) {if (dpth[pnt[a][i]] >= dpth[b]) {if (val != maxx[a][i])res = std::max(res, maxx[a][i]);elseres = std::max(res, minn[a][i]);a = pnt[a][i];}}return res;}
} tr;int fa[100010];
int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }void Kruskal() {int tot = 0;std::sort(e + 1, e + m + 1);for (int i = 1; i <= n; i++) fa[i] = i;for (int i = 1; i <= m; i++) {int a = find(e[i].u);int b = find(e[i].v);if (a != b) {fa[a] = b;tot++;tr.insedge(e[i].u, e[i].v, e[i].val);sum += e[i].val;used[i] = 1;}if (tot == n - 1) break;}
}int main() {std::ios::sync_with_stdio(0);std::cin.tie(0);std::cout.tie(0);std::cin >> n >> m;for (int i = 1; i <= m; i++) {int u, v, val;std::cin >> u >> v >> val;e[i] = (Edge){u, v, val};}Kruskal();long long ans = INF64;tr.dfs(1, 0);for (int i = 1; i <= m; i++) {if (!used[i]) {int _lca = tr.lca(e[i].u, e[i].v);// 找到路径上不等于 e[i].val 的最大边权long long tmpa = tr.query(e[i].u, _lca, e[i].val);long long tmpb = tr.query(e[i].v, _lca, e[i].val);// 这样的边可能不存在,只在这样的边存在时更新答案if (std::max(tmpa, tmpb) > -INF)ans = std::min(ans, sum - std::max(tmpa, tmpb) + e[i].val);}}// 次小生成树不存在时输出 -1std::cout << (ans == INF64 ? -1 : ans) << '\n';return 0;
}

LCA

倍增模板:O(nlogn)

#include<cstring>
#include<algorithm>
#include<cstdio>
#include<vector>using namespace std;int t,n,m;const int N = 4e4+10;
int fa[N][31],cost[N][31],deep[N];//fa[i][j]表示第i个点向上跳2^j次方位置的祖先节点,cost[i][j]表示向上跳路径的花费,deep[i]表示的是第i个节点的深度
vector<int> V[N],W[N];void init() {//初始化for(int i = 0;i <= n; ++i) {for(int j = 0;j <= 30; ++j)fa[i][j] = cost[i][j] = 0;deep[i] = 0;V[i].clear(),W[i].clear();}
}void dfs(int root, int faa) {fa[root][0] = faa;deep[root] = deep[fa[root][0]] + 1;for(int i = 1; i <= 30; ++i) {fa[root][i] = fa[fa[root][i-1]][i-1];cost[root][i] = cost[fa[root][i-1]][i-1] + cost[root][i-1];}int len = V[root].size();for(int i = 0;i < len; ++i) {if(V[root][i] == faa) continue;cost[V[root][i]][0] = W[root][i];dfs(V[root][i],root);}
}int LCA(int x,int y) {if(deep[x] > deep[y]) swap(x,y);int temp = deep[y] - deep[x];//深度差int ans = 0;for(int j = 0;temp;++j,temp>>=1) {//计算深度差的权值差if(temp & 1) ans += cost[y][j],y = fa[y][j];}if(x == y) return ans;//如果节点一样直接返回值for(int i = 30;i >= 0 && x != y; --i) {if(fa[x][i] != fa[y][i]) {ans += cost[x][i] + cost[y][i];x = fa[x][i];y = fa[y][i];}}ans += cost[x][0] + cost[y][0];return ans;
}int main()
{int t;scanf("%d",&t);while(t--) {scanf("%d%d",&n,&m);init();for(int i = 1;i < n; ++i) {int u,v,w;scanf("%d%d%d",&u,&v,&w);V[u].push_back(v);V[v].push_back(u);W[u].push_back(w);W[v].push_back(w);   }dfs(1,0);while(m--) {int u,v;scanf("%d%d",&u,&v);printf("%d\n",LCA(u,v));}}return 0;
}

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]];}}//开始回溯将状态标记为2st[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;
}

二分图

匈牙利算法:O(n×m)O(n\times m)O(n×m)

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;
} 

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,然后求最长路就是解得最小值。

求树的重心

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;
}

无向图割点和割边

Tarjan 算法求割点

vector<gg> graph[MAX];
// dfn表示每个结点的搜索次序
gg dfn[MAX], low[MAX];
bool visit[MAX], ans[MAX];  // ans标记该结点是否为割点
gg cnt = 0;  // cnt表示搜索次序
void dfs(gg v, gg fa) {visit[v] = true;low[v] = dfn[v] = ++cnt;gg child = 0;for (gg i : graph[v]) {if (not visit[i]) {++child;dfs(i, v);low[v] = min(low[v], low[i]);if (fa != v and low[i] >= dfn[v] and not ans[v]) {ans[v] = true;}} else if (i != fa) {low[v] = min(low[v], dfn[i]);}}if (fa == v and child >= 2 and not ans[v]) {ans[v] = true;}
}
void tarjan() {for (gg i = 1; i <= ni; ++i) {if (not visit[i]) {cnt = 0;dfs(i, i);}}
}

Tarjan 算法求桥

vector<gg> graph[MAX];
// dfn表示每个结点的搜索次序
gg dfn[MAX], low[MAX], father[MAX];
bool ans[MAX];  // 如果ans[i]为true,则表示(father[x],x)为割边
gg cnt = 0;  // cnt表示搜索次序
void dfs(gg v, gg fa) {father[v] = fa;low[v] = dfn[v] = ++cnt;for (gg i : graph[v]) {if (dfn[i] == 0) {dfs(i, v);low[v] = min(low[v], low[i]);if (low[i] > dfn[v]) {ans[i] = true;}} else if (dfn[i] < dfn[v] and i != fa) {low[v] = min(low[v], dfn[i]);}}
}
void tarjan() {for (gg i = 1; i <= ni; ++i) {if (dfn[i] == 0) {cnt = 0;dfs(i, i);}}
}

Tarjan算法之割边

定义

(在无向图中):在一个连通图中,如果删去其中一条边后,连通块的数量会增多,那么我们称这条边为桥或者是割边.

算法

tarjan,只需要判定low[v]>dfn[u]即可(u为父,v为子)
解释:如果子节点在不走原路情况下到不了父节点或父节点之前的点,那么子节点只能走原路回到父节点及之前节点,原路为必经之路,断掉就会产生新的个联通块,符合割边定义。

例题:

HDU4738

code

#include<iostream>
#include<cstdio>
#include<cmath>
#include<queue>
#include<cstring>
#include<algorithm>
#define lson x<<1
#define rson x<<1|1
#define ll long long
#define rint register int
#define mid ((st[x].l + st[x].r) >> 1)
using namespace std;
template <typename xxx> inline void read(xxx &x) {char c = getchar(),f = 1;x = 0;for(;c ^ '-' && !isdigit(c);c = getchar());if(c == '-') c = getchar(),f = -1;for(;isdigit(c);c = getchar()) x = (x<<1) + (x<<3) + (c ^ '0');x *= f;
}
template<typename xxx>void print(xxx x)
{if(x<0){putchar('-');x=-x;}if(x>9) print(x/10); putchar(x%10+'0');
}
const int maxn = 2002;
const int inf = 0x7fffffff;
const int mod = 1e9 + 7;
struct edge {int to,last,val;
//  int fg;
}e[2000002];
int head[maxn],tot;
inline void add(int from,int to,int val) {++tot;e[tot].to = to;
//  e[tot].fg = 0;e[tot].val = val;e[tot].last = head[from];head[from] = tot;
}
int n,m;
int dfn[maxn],low[maxn],cnt;
int mark[2000002],mk;
inline void tarjan(int x,int in_edge) {//通过in_edge到达x dfn[x] = low[x] = ++cnt;for(rint i = head[x];i;i = e[i].last) {if(!dfn[e[i].to]) {tarjan(e[i].to,i);if(low[x] > low[e[i].to]) low[x] = low[e[i].to];if(low[e[i].to] > dfn[x]) {//子节点无法到达我及我之前,则联系我与子节点的边为桥
//这个判断可以防重边对桥的干扰,假设原图有一个桥,tarjan时绝对无法回到父节点及之前节点,
//但是有重边时 子节点通过重边“回到父节点”更新low[e[i].to] == dfn[x],结果无法被判为桥
//              e[i].fg = e[i^1].fg = 1;//可这样记录重边 mark[++mk] = e[i].val;}}else if(i ^ (in_edge ^ 1) && dfn[e[i].to] < low[x]) low[x] = dfn[e[i].to];//保证无重边时不会被父亲更新 }
}
int main()
{while(1) {tot = 1;mk = 0;cnt = 0;read(n);read(m);if(!n && !m) break;memset(dfn,0,sizeof(dfn));memset(low,0,sizeof(low));memset(head,0,sizeof(head));for(rint i = 1;i <= m; ++i) {int a,b,c;read(a);read(b);read(c);add(a,b,c);add(b,a,c);}   int k = 0;for(rint i = 1;i <= n; ++i) {if(!dfn[i]) {tarjan(i,0);++k;}}if(mk == 0) {//无桥, printf("-1\n");} else {stable_sort(mark + 1,mark + mk + 1);if(k > 1) printf("0\n");else if(mark[1])printf("%d\n",mark[1]);else printf("1\n");//没人守也要派一个人去占领 }}    return 0;
}
/*
6 8
1 2 1
2 1 2
1 3 3
3 4 4
4 1 5
2 5 6
2 6 7
5 6 86 7
1 2 1
1 3 3
3 4 4
4 1 5
2 5 6
2 6 7
5 6 8
*/

树的直径

const int N = 10000 + 10;int n, c, d[N];
vector<int> E[N];void dfs(int u, int fa) {for (int v : E[u]) {if (v == fa) continue;d[v] = d[u] + 1;if (d[v] > d[c]) c = v;dfs(v, u);}
}int main() {scanf("%d", &n);for (int i = 1; i < n; i++) {int u, v;scanf("%d %d", &u, &v);E[u].push_back(v), E[v].push_back(u);}dfs(1, 0);d[c] = 0, dfs(c, 0);printf("%d\n", d[c]);return 0;
}

数论

数论常见公式

卡特兰数

错排公式

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

常用前n项和公式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JlqWmUvA-1641292542714)(D:\Typora\data\nh.png)]

GCD

GCD的四种方式,辗转相除法和更相减损法的循环写法和递归写法,但是一般来说要用gcd,直接调用__gcd函数就可

/*作者:MangataGCD的四种写法
*/
#include<cstdio>
int gcd_1(int a,int b){//辗转相除法 循环写法 while(b > 0) {int t = a%b;a = b;b = t;}return a;
}
int gcd_2(int a,int b) {//辗转相除法 递归写法 return b?gcd_2(b,a%b) : a;
}
int gcd_3(int a,int b) {//更相减损法 递归写法 if(a == 0)return b;if(b == 0)return a; if(a == b)return a;if(a > b)return gcd_3(a-b,b);if(a < b)return gcd_3(b-a,a);
}
int gcd_4(int a,int b) {//更相减损法 循环写法 if(a == 0)return b;if(b == 0)return a;while(a != b) {if(a > b)a -= b;else{int t = a;a = b - a;b = t;}}return a;
}
int main()
{int a,b;while(~scanf("%d%d",&a,&b))printf("gcd_1=%d\ngcd_2=%d\ngcd_3=%d\ngcd_4=%d\n",gcd_1(a,b),gcd_2(a,b),gcd_3(a,b),gcd_4(a,b));
}

拓展GCD

关于拓展GCD,我觉得比较核心的是贝祖定理,ax+by=gcd(a,b),方便我们求解不定方程,板子选取的题目是青蛙的约会

//青蛙的约会#include<cstdio>
#include<algorithm>
#define ll long long
//返回的是GCD(a,b)
ll extgcd(ll a, ll b, ll &x0, ll &y0) {//注意这里是引用if (!b) {x0 = 1, y0 = 0;return a;}ll d = extgcd(b, a%b, x0, y0);ll t = x0;x0 = y0;y0 = t - a / b * y0;return d;
}int main()
{ll x,y,m,l,x0,y0,n;while(~scanf("%lld%lld%lld%lld%lld",&x, &y, &m, &n, &l)) {x0 = 0, y0 = 0;ll c = x - y;ll a = n - m;if(a < 0) {a = -a;c = -c;}ll d = extgcd(a, l, x0, y0);if (c % d)puts("Impossible");    elseprintf("%lld\n",(c/d*x0 % (l/d) + l/d) % (l/d));}return 0;
}

素数判断

朴素素数判断

/*作者:Mangata朴素素数判断模板
*/
#include<cstdio>
#include<algorithm>
#include<cstring>bool is_prime(int x) {if(x == 0 || x == 1)return false;for(int i = 2; i*i <= x; ++i) {if(x % i == 0)return false;}return true;
}int main()
{while(~scanf("%d",&x)) {if (is_prime(x))puts("Yes");elseputs("No");}return 0;
}

埃式筛

/*作者:Mangata埃式筛模板
*/
//时间复杂度 :O(n*log(log(n)))
#include<cstdio>
#include<algorithm>
#include<cstring>const int N = 10000005;
bool vis[N] = {true,true};//初始化 int main()
{int n,x;//离线处理 for(int i = 2; i*i <= N; ++i) {if (!vis[i]) {for(int j = i*2; j <= N; j += i) //把素数的倍数筛掉 vis[j] = true;        }}while(~scanf("%d",&x)) {if (vis[x])puts("No");elseputs("Yes");
}return 0;
}

欧拉筛

/*作者:Mangata欧拉筛模板
*/
//时间复杂度为  O(n)
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>const int N = 10000005;
int prime[N];
bool vis[N];void get_prime() {memset(vis,true,sizeof vis);memset(prime,0,sizeof prime);vis[0] = vis[1] = false;for(int i = 2; i <= N; ++i) {if (vis[i]) {prime[++prime[0]] = i;}for(int j = 1;j <= prime[0] && i*prime[j] <= N; ++j) {vis[i * prime[j]] = false;if (i % prime[j] == 0) //避免重复筛选               break;}    }
}int main()
{int n;get_prime();while(~scanf("%d",&n)) {if (vis[n])puts("Yes");elseputs("No");}return 0;
}

快速幂&龟速乘

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
//快速幂
ll quick_pow(ll x, ll n, ll mod) {ll res = 1;while(n > 0) {if(n & 1)    res = (res * x)% mod;x = (x * x) % mod;n >>= 1;}return res;
}
//快速乘
ll quick_pow2(ll x, ll n, ll mod) {ll res = 0;x%= mod;n%= mod;while(n) {if(n & 1) {res = (res + x) % mod;}x = (x%mod + x%mod) % mod;n >>= 1;}return res%mod;
}
int main()
{ll a,b,c,n;
//    while(~scanf("%lld%lld%lld",&a,&b,&c)) {//        printf("%lld\n",quick_pow(a,b,c));
//    }ll key,temp; while(~scanf("%lld%lld",&n,&c)) {scanf("%lld",&key);key %= c;for(ll i = 1; i < n; ++i) {scanf("%lld",&temp);key = quick_pow2(key,temp,c);}printf("%lld\n",key);}return 0;
}

组合数模板

ll qpow(ll a,ll b,ll c) {ll ans = 1;while(b) {if(b & 1) ans = (ans * a) % c;b >>= 1;a = (a * a) % c;}return ans;
}ll inv(ll x,ll c) {//逆元return qpow(x,c-2,c);
}ll C(ll a,ll b, ll c) {//组合数C(b,a)  a>=bll ans = 1;for(int i = 1;i <= b; ++i) {ans = ans * (a-i+1) % c;ans =  ans * inv(i,c) % c;}return ans;
}

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;
} 

高斯消元

不带模数的求行列式

void det() {const double EPS = 1E-9;int n;vector<vector<double> > a(n, vector<double>(n));double det = 1;for (int i = 0; i < n; ++i) {int k = i;for (int j = i + 1; j < n; ++j)if (abs(a[j][i]) > abs(a[k][i])) k = j;if (abs(a[k][i]) < EPS) {det = 0;break;}swap(a[i], a[k]);if (i != k) det = -det;det *= a[i][i];for (int j = i + 1; j < n; ++j) a[i][j] /= a[i][i];for (int j = 0; j < n; ++j)if (j != i && abs(a[j][i]) > EPS)for (int k = i + 1; k < n; ++k) a[j][k] -= a[i][k] * a[j][i];}cout << det;
}

带模数的求行列式

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define mod 1000000007
ll n;
const int N = 1e2+10;ll A[N][N];
ll AA;ll quick_pow(ll a, ll n) {ll ans = 1;while(n) {if(n & 1) ans = ans * a % mod;a = a * a % mod;n >>= 1;}return ans;
}ll inv(ll a) {return quick_pow(a, mod - 2);
}void gauss(){ll ans = 1;for(int i = 1; i <= n; i++){for(int j = i; j <= n; j++) {if(A[j][i]){for(int k = i; k <= n; k++) swap(A[i][k], A[j][k]);if(i != j) ans = -ans;break;}}
//        if(!A[i][i]) return 0;for(ll j = i + 1, iv = inv(A[i][i]); j <= n; j++) {ll t = A[j][i] * iv % mod;for(int k = i; k <= n; k++)A[j][k] = (A[j][k] - t * A[i][k] % mod + mod) % mod;}ans = (ans * A[i][i] % mod + mod) % mod;}if(ans == AA) puts("+");else puts("-");
//    return ans;
}
char ch[100000];int main()
{int t;scanf("%d",&t);while(t--) {AA = 0;memset(A,0,sizeof A);scanf("%d%s",&n,&ch);bool fg = false;for(int i = 0,len = strlen(ch);i < len; ++i) {if(ch[i] == '-')  {fg = true;continue;}AA = (AA * 10 + ch[i] - '0') % mod;}if(fg) AA = -AA;for(int i = 1;i <= n; ++i) {for(int j = 1;j <= n; ++j) {scanf("%lld",&A[i][j]);}}gauss();}return 0;
}

Miller Rabin算法

用于判断一个数字是否是素数(这个数尽量大一点,有一定的概率出错,因此也叫做素数检测算法)

typedef long long LL;LL quickMul(LL a, LL n, LL mod) {LL ans = 0;while (n) {if (n & 1) ans = (ans + a) % mod;a = (a + a) % mod, n >>= 1;}return ans;
}LL quickPow(LL a, LL n, LL mod) {LL ans = 1;while (n) {if (n & 1) ans = quickMul(ans, a, mod);a = quickMul(a, a, mod), n >>= 1;}return ans;
}LL random(LL n) { // 产生2到n-2的随机数return double(rand()) / RAND_MAX * (n - 4) + 2;
}bool millerRobbin(LL n, int cnt) {//cnt表示测试的次数,越大准确度理论上越高if (n == 2 || n == 3) return true;if (n < 2 || !(n & 1)) return false;int t = 0;LL a, x, y, u = n - 1;while (!(u & 1)) ++t, u >>= 1;for (int i = 0; i < cnt; ++i) {a = random(n), x = quickPow(a, u, n);for (int j = 0; j < t; ++j, x = y) {y = quickMul(x, x, n);if (y == 1 && x != 1 && x != n - 1) return false;}if (x != 1) return false;}return true;
}

字符串

Manacher模板

Manacher算法主要应用于回文字符串上面,通过字符串翻倍我们能很轻松的通过对称的关系找到子字符串的回文长度。

/*作者:Mangatamanacger模板
*/
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
#define N 210005char S[N],str[N*2+5];
int len1,len2,ans,p[N*2+5];void init() {//数组初始化,即数组长度翻倍 str[0] = '$';//为了防止数组越界 str[1] = '#';for(int i = 0; i < len1; ++i){str[i * 2 + 2] = S[i];str[i * 2 + 3] = '#';}len2 = len1 * 2 + 2;str[len2] = '*';
}void manacher() {//manacher init();int id = 0,mx = 0;//mx表示的是当前计算回文串最右边字符的最大值 for(int i=0; i < len2; ++i){p[i]=mx > i?min(p[id*2-i],mx-i) : 1;//p[i]=mx>i?min(p[id*2-i],m-i):1; for(; str[i+p[i]] == str[i-p[i]]; p[i]++);//如果字符串对称则对称长度增加 if(p[i]+i > mx)//如果大于当前的最大长度则更新 mx = p[i]+i, id = i;}
}int main() {while(~scanf("%s",S)){len1 = strlen(S);manacher();int maxlen = 0;for(int i = 0; i < len2; ++i)maxlen = max(p[i], maxlen);printf("%d\n",maxlen-1);}return 0;
}

KMP模板

KMP是一个字符串匹配算法,通过此算法能达到线性的匹配时间复杂度,通过对字串求next数组,让我们字符串不回溯(或者说是减少步骤)

/*作者:Mangatamanacger模板
*/
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;#define N 10005
int nextt[N];
char S[N],T[N];
int len1,len2;void get_next() {//获取next数组 int i = 0,j = -1;nextt[0] = -1;//很重要 while(i < len1){if(j == -1 || S[i] == S[j])nextt[++i] = ++j;elsej = nextt[j];}
}int kmp(){//返回的是子串在主串中存在的次数 int cnt = 0;//表示的是子串在主串中存在的次数 int i = 0,j = 0;get_next();while(i < len2){if(j == -1 || T[i] == S[j])i++,j++;else j = nextt[j];if(j == len1)//表示的是子串在主串中存在的次数 j=0,cnt++;//子串指针归零 }return cnt;
}int main(){while(~scanf("%s",T)){if(T[0]=='#')break;scanf("%s",S);len1 = strlen(S);len2 = strlen(T);printf("%d\n",kmp());}return 0;
}

字符串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];
}

AC自动机

求模式串在文本串里面匹配的次数

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#define maxn 1000005
#define maxm 500005
using namespace std;
int tree[maxm][26],fail[maxm],flag[maxn],cnt;
char str[maxn];
void init()
{memset(fail,0,sizeof(fail));memset(tree,0,sizeof(tree));memset(flag,0,sizeof(flag));cnt=0;
}
void insert(char *ch)
{int len=strlen(ch);int root=0;for(int i=0;i<len;++i){int id=ch[i]-'a';if(!tree[root][id])tree[root][id]=++cnt;root=tree[root][id];}flag[root]++;
}void getfail()
{queue<int>q;for(int i=0;i<26;++i){if(tree[0][i]){fail[tree[0][i]]=0;q.push(tree[0][i]);}}while(!q.empty()){int now=q.front();q.pop();for(int i=0;i<26;++i){if(tree[now][i]){fail[tree[now][i]]=tree[fail[now]][i];q.push(tree[now][i]);}else{tree[now][i]=tree[fail[now]][i];}}}
}int find(char *ch)
{int now=0,ans=0;int len=strlen(ch);for(int i=0;i<len;++i){int id=ch[i]-'a';now=tree[now][id];for(int j=now;j&&flag[j]!=-1;j=fail[j]){ans+=flag[j];flag[j]=-1;}}return ans;
}
int main(void)
{int t,n;char ss[60];scanf("%d",&t);while(t--){init();scanf("%d",&n);for(int i=0;i<n;++i){scanf("%s",ss);insert(ss);}getfail();scanf("%s",str);printf("%d\n",find(str));}
}

DP动态规划

背包模板

目前只写三种常用的背包模板(01、完全、多重),主要也是其他的背包都比较抽象,不好直接写代码。

01背包

/*作者:Mangata01背包问题模板 滚动数组优化
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 10005int f[N];
int v[N],w[N];
int n,V;int main()
{scanf("%d%d",&n,&V);for(int i = 1; i <= n; ++i)    scanf("%d%d",&w[i],&v[i]);for(int i = 1; i <= n; ++i){for(int j = V; j >= w[i]; --j){f[j] = max(f[j],f[j-w[i]]+v[i]);}}printf("%d\n",f[V]); return 0;
}

完全背包

/*作者:Mangata完全背包问题模板 滚动数组优化
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 10005int f[N];
int v[N],w[N];
int n,V;int main()
{scanf("%d%d",&n,&V);for(int i = 1; i <= n; ++i)    scanf("%d%d",&w[i], &v[i]);for(int i = 1; i <= n; ++i){for(int j = w[i]; j <= V; ++j){f[j] = max(f[j], f[j-w[i]]+v[i]);}}printf("%d\n",f[V]); return 0;
}

多重背包

/*作者:Mangata多重背包问题模板 二进制枚举优化
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 10005int f[N];
int v[N],w[N],p[N];
int n,V;int main()
{scanf("%d%d",&n,&V);for(int i = 1; i <= n; ++i)    scanf("%d%d%d",&w[i],&v[i],&p[i]);for(int i = 1; i <= n; ++i){int num = min(p[i],V/w[i]);for(int k = 1; num > 0; k <<= 1){if(k > num)k = num;num -= k;for(int j = V; j >= w[i]*k; --j)f[j] = max(f[j], f[j-w[i]*k]+v[i]*k);}}printf("%d\n", f[V]); return 0;
}

分组背包

n件物品,总重量为m,a、b、c分别表示每个物品的重量,利用价值,组数,求最大利用价值(每个组至多选一个)

#include <bits/stdc++.h>//万能库
using namespace std;
const int M=1010,N=1010;
int n,m;
int f[M],a[N],b[N],c[101][20],cc[101],cn;
int main()
{scanf("%d%d",&m,&n);for(int i=1;i<=n;i++){int C;scanf("%d%d%d",&a[i],&b[i],&C);cn=max(cn,C);//cn记录共有几组cc[C]++;//cc[]记录第C组共有几件物品c[C][cc[C]]=i;//c[][]记录第C组第i件物品的的序号}for(int i=1;i<=cn;i++)//枚举cn个组for(int j=m;j>=0;j--)//枚举容量for(int k=1;k<=cc[i];k++)//枚举各组中物品的序号if(j>=a[c[i][k]])f[j]=max(f[j],f[j-a[c[i][k]]]+b[c[i][k]]);//套用状态转移方程printf("%d\n",f[m]);//CE
}

单调队列优化的背包:

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;
}

超大背包问题

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;const int N = 45;
typedef long long ll;
ll w[N],v[N],W;
int n;struct Node {ll w,v;
}ps[1 << (N/2)];//表示的是第一个堆枚举的可能的情况总数bool cmp(Node a, Node b) {//排序函数,因为待会我们搜索的时候是按照W的大小进行二分if(a.w != b.w)return a.w < b.w;return a.v > b.v;
}int erfen(int l,int r,ll k) {//二分搜索while(l + 1 < r) {int mid = l + r >> 1;if(ps[mid].w <= k) {l = mid;}else {r = mid;}}
//此时的r表示的是大于k的第一个位置return r - 1;
}void slove() {int len1 = n / 2;for(int i = 0, len = 1 << len1; i < len; ++i) {//二进制枚举第一个堆可能的情况int tempw = 0, tempv = 0;for(int j = 0;j < len1; ++j) {if(i >> j & 1) {//是否选取tempw += w[j];tempv += v[j];}}ps[i] = (Node){tempw,tempv};}sort(ps,ps+(1<<len1),cmp);//排序int m = 1;for(int i = 1, len = 1 << len1; i < len; ++i) {//这里筛出无用的选取组合,if(ps[m - 1].v < ps[i].v) {//这样能让ps从0到m-1的元素都是w和v升序的ps[m++] = ps[i];   //也就是让后一个元素的v一定是大于前一个元素的v}}ll ans = 0;for(int i = 0,len = (1 << (n - len1)); i < len; ++i) {//二进制枚举第二个堆的选取情况int tempw = 0, tempv = 0;for(int j = 0; j < n - len1; ++j) {if(i >> j & 1) {tempw += w[len1 + j];tempv += v[len1 + j];}}if(tempw <= W) {int loc = erfen(-1,m,W-tempw);ll key = 0;if(loc != -1)//可能第一个堆什么也不选就满了key = ps[loc].v;ans = max(ans,key + tempv);//选取最优情况}}printf("%lld\n",ans);
}int main()
{scanf("%d",&n);for(int i = 0; i < n; ++i) {scanf("%lld",&w[i]);}for(int i = 0; i < n; ++i) {scanf("%lld",&v[i]);}scanf("%lld",&W);slove();return 0;
}

数位DP

例一

给定两个正整数 aa 和 bb,求在 [a,b][a,b] 中的所有整数中,每个数码(digit)各出现了多少次。

#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;const int N = 15;
ll f[N][2][N][2];
int num[N];  //num来存这个数每个位子上的数码/*
记忆化搜索。
len是当前为从高到低第几位。issmall表示当前位是否和num[len]相等,0是相等,1是不相等。
sum表示当前数字出现的次数。zero表示之前是否是前导0。d是当前在算的数码。
*/
ll dfs(int len, bool issmall, int sum, bool zero, int d)
{ll ret = 0;if (len == 0) return sum;  //边界条件if (f[len][issmall][sum][zero] != -1) return f[len][issmall][sum][zero];  //记忆化for (int i = 0; i < 10; i ++){if (!issmall && i > num[len]) break;/*由于我们是从高位到低位枚举的,所以如果之前一位的数码和最大数的数码相同,这一位就只能枚举到num[len];否则如果之前一位比最大数的数码小,那这一位就可以从0~9枚举了。*/ret += dfs(len-1, issmall || (i<num[len]), sum+((!zero || i) && (i==d)), zero && (i == 0), d);/*继续搜索,数位减一,issmall的更新要看之前有没有相等,且这一位有没有相等;sum的更新要看之前是否为前导0或者这一位不是0;zero的更新就看之前是否为前导0且这一位继续为0;d继续传进去。*/}f[len][issmall][sum][zero] = ret;//记忆化,把搜到的都记下来return ret;
}ll solve(ll x, int d)
{int len = 0;while (x){num[++ len] = x%10;x /= 10;} //数字转数位memset(f, -1, sizeof f); //初始化return dfs(len, 0, 0, 1, d); //开始在第len位上,最高位只能枚举到num[len]所以issmall是0,sum=0,有前导0。
}int main()
{ll a, b; //注意都要开long longscanf("%lld%lld", &a, &b);for (int i = 0; i < 10; i ++)printf("%lld%c", solve(b, i)-solve(a-1, i), i == 9 ? '\n' : ' ');return 0;
}

例二

1 到 N 中有多少个数满足其二进制表示中恰好有 K 个 1。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
int a[70];
ll dp[70][70], N;
int K;ll dfs(int p, int limit, int k) {if (p == 0) return k == K;if (!limit && ~dp[p][k]) return dp[p][k];int up = limit ? a[p] : 1;ll ans = 0; for (int i = 0; i <= up; ++i) {ans += dfs(p - 1, limit && up == i, k + (i == 1));}if (!limit) dp[p][k] = ans;return ans;
}ll calc(ll v) {memset(dp, -1, sizeof(dp));int p = 0;do {a[++p] = v & 1;v /= 2;} while (v);return dfs(p, 1, 0);
} int main() {cin >> N >> K;cout << calc(N) << endl; return 0;
}

LIS(最长上身子序列)nlogn

template<typename T>
int LIS(vector<T> a) {vector<T> dp;for(auto x : a) {auto it = upper_bound(dp.begin(), dp.end(), x); // > : lower, >= : upperif(it == dp.end()) dp.push_back(x);else *it = x;}return dp.size();
}

STL

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());
v.insert(v.begin()+4,3); 在第五个元素前插入一个元素

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

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的数量,因为集合中的元素不能重复,因此只能返回0或1 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所指位置元素,无返回值。

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都是唯一的,只会返回0或1
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

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的公有数据成员。

algorithm常用函数

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());

sort()函数

降序排列sort(s+1,s+1+n,greater());

求第k小数

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

二分函数:

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

在从小到大的排序数组中:

  • 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,得到找到数字在数组中的下标

注意一点这个lower_boundupper_bound只能用于非降序列,如果要用于非升序列的那要加一个greater <int> ()

计算几何

计算几何基本模板

#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是否等于0if(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<piLine(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=0Line(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:cddouble 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:cddouble 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;
}

半平面交

点向式求半平面交

#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;
}

两点式求半平面交一

const double INF = 1e12;
const double eps = 1e-8;
int n,m;
int sgn(double x){  //判断x是否等于0if(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:cddouble 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;
}

两点式求半平面交模板二

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是否等于0if(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:cddouble 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;
} 

N圆面积并&&交

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef vector <int> VI;
const int INF = 0x3f3f3f3f;
const double eps = 1e-10;
const int MOD = 100000007;
const int MAXN = 1000010;
const double PI = acos(-1.0);
#define sqr(x) ((x)*(x))
const int N = 1010;
double area[N];
int n;
int dcmp(double x){if (x < -eps) return -1;else return x > eps;
}
struct cp{double x, y, r, angle;int d;cp() {}cp(double xx, double yy, double ang = 0, int t = 0){x = xx;y = yy;angle = ang;d = t;}void get(){scanf("%lf%lf%lf", &x, &y, &r);d = 1;}
} cir[N], tp[N * 2];
double dis(cp a, cp b){return sqrt(sqr(a.x - b.x) + sqr(a.y - b.y));}
double cross(cp p0, cp p1, cp p2){return (p1.x - p0.x) * (p2.y - p0.y) - (p1.y - p0.y) * (p2.x - p0.x);}
int CirCrossCir(cp p1, double r1, cp p2, double r2, cp &cp1, cp &cp2){double mx = p2.x - p1.x, sx = p2.x + p1.x, mx2 = mx * mx;double my = p2.y - p1.y, sy = p2.y + p1.y, my2 = my * my;double sq = mx2 + my2, d = -(sq - sqr(r1 - r2)) * (sq - sqr(r1 + r2));if (d + eps < 0) return 0;if (d < eps) d = 0;else d = sqrt(d);double x = mx * ((r1 + r2) * (r1 - r2) + mx * sx) + sx * my2;double y = my * ((r1 + r2) * (r1 - r2) + my * sy) + sy * mx2;double dx = mx * d, dy = my * d;sq *= 2;cp1.x = (x - dy) / sq;cp1.y = (y + dx) / sq;cp2.x = (x + dy) / sq;cp2.y = (y - dx) / sq;if (d > eps) return 2;else return 1;
}
bool circmp(const cp& u, const cp& v){return dcmp(u.r - v.r) < 0;}
bool cmp(const cp& u, const cp& v){if (dcmp(u.angle - v.angle)) return u.angle < v.angle;return u.d > v.d;
}
double calc(cp cir, cp cp1, cp cp2){double ans = (cp2.angle - cp1.angle) * sqr(cir.r)- cross(cir, cp1, cp2) + cross(cp(0, 0), cp1, cp2);return ans / 2;
}
void CirUnion(cp cir[], int n){cp cp1, cp2;sort(cir, cir + n, circmp);for (int i = 0; i < n; ++i)for (int j = i + 1; j < n; ++j)if (dcmp(dis(cir[i], cir[j]) + cir[i].r - cir[j].r) <= 0)cir[i].d++;for (int i = 0; i < n; ++i){int tn = 0, cnt = 0;for (int j = 0; j < n; ++j){if (i == j) continue;if (CirCrossCir(cir[i], cir[i].r, cir[j], cir[j].r,cp2, cp1) < 2) continue;cp1.angle = atan2(cp1.y - cir[i].y, cp1.x - cir[i].x);cp2.angle = atan2(cp2.y - cir[i].y, cp2.x - cir[i].x);cp1.d = 1;tp[tn++] = cp1;cp2.d = -1;tp[tn++] = cp2;if (dcmp(cp1.angle - cp2.angle) > 0) cnt++;}tp[tn++] = cp(cir[i].x - cir[i].r, cir[i].y, PI, -cnt);tp[tn++] = cp(cir[i].x - cir[i].r, cir[i].y, -PI, cnt);sort(tp, tp + tn, cmp);int p, s = cir[i].d + tp[0].d;for (int j = 1; j < tn; ++j){p = s;s += tp[j].d;area[p] += calc(cir[i], tp[j - 1], tp[j]);}}
}
void solve(){scanf("%d", &n);double sum = 0;for (int i = 0; i < n; ++i)cir[i].get();memset(area, 0, sizeof(area));CirUnion(cir, n);for (int i = 1; i <= n; ++i)    //去掉重复计算的area[i] -= area[i + 1];     //area[i]为重叠了i次的面积double tot = 0;//tot 为总面积for(int i=1; i<=n; i++) tot += area[i];
//    cout<<area[n]<<endl;    //重叠了n次就是所求N圆相交面积printf("%f\n", tot);  //这句就是求总面积
}
int main(){solve();return 0;
}
/*
in
3
1 0 2
-1 0 2
0 1 2
Out
4.20324 相交面积
//22.929505 相并面积
*/

凸包&判凸包相交

#include<cstdio>
#include<iostream>
#include<vector>
#include<cmath>
#include<algorithm>
using namespace std;const double eps = 1e-10;
double dcmp(double x) {if(fabs(x) < eps) return 0;else return x < 0 ? -1 : 1;
}struct Point {double x, y;Point(double x=0, double y=0):x(x),y(y) {}
};typedef Point Vector;Vector operator - (const Point& A, const Point& B) {return Vector(A.x-B.x, A.y-B.y);
}double Cross(const Vector& A, const Vector& B) {return A.x*B.y - A.y*B.x;
}double Dot(const Vector& A, const Vector& B) {return A.x*B.x + A.y*B.y;
}bool operator < (const Point& p1, const Point& p2) {return p1.x < p2.x || (p1.x == p2.x && p1.y < p2.y);
}bool operator == (const Point& p1, const Point& p2) {return p1.x == p2.x && p1.y == p2.y;
}bool SegmentProperIntersection(const Point& a1, const Point& a2, const Point& b1, const Point& b2) {double c1 = Cross(a2-a1,b1-a1), c2 = Cross(a2-a1,b2-a1),c3 = Cross(b2-b1,a1-b1), c4=Cross(b2-b1,a2-b1);return dcmp(c1)*dcmp(c2)<0 && dcmp(c3)*dcmp(c4)<0;
}bool OnSegment(const Point& p, const Point& a1, const Point& a2) {return dcmp(Cross(a1-p, a2-p)) == 0 && dcmp(Dot(a1-p, a2-p)) < 0;
}// 点集凸包
// 如果不希望在凸包的边上有输入点,把两个 <= 改成 <
// 如果不介意点集被修改,可以改成传递引用
vector<Point> ConvexHull(vector<Point> p) {// 预处理,删除重复点sort(p.begin(), p.end());p.erase(unique(p.begin(), p.end()), p.end());int n = p.size();int m = 0;vector<Point> ch(n+1);for(int i = 0; i < n; i++) {while(m > 1 && Cross(ch[m-1]-ch[m-2], p[i]-ch[m-2]) <= 0) m--;ch[m++] = p[i];}int k = m;for(int i = n-2; i >= 0; i--) {while(m > k && Cross(ch[m-1]-ch[m-2], p[i]-ch[m-2]) <= 0) m--;ch[m++] = p[i];}if(n > 1) m--;ch.resize(m);return ch;
}//点是否在凸包内
int IsPointInPolygon(const Point& p, const vector<Point>& poly) {int wn = 0;int n = poly.size();for(int i=0; i<n; ++i) {const Point& p1 = poly[i];const Point& p2 = poly[(i+1)%n];if(p1 == p || p2 == p || OnSegment(p, p1, p2)) return -1;//在边界上int k = dcmp(Cross(p2-p1, p-p1));int d1 = dcmp(p1.y - p.y);int d2 = dcmp(p2.y - p.y);if(k > 0 && d1 <= 0 && d2 > 0) wn++;if(k < 0 && d2 <= 0 && d1 > 0) wn--;}if(wn != 0) return 1;return 0;
}//判断2个凸包是否相交
bool ConvexPolygonDisjoint(const vector<Point> ch1, const vector<Point> ch2) {int c1 = ch1.size();int c2 = ch2.size();for(int i=0; i<c1; ++i)if(IsPointInPolygon(ch1[i], ch2) != 0) return false;for(int i=0; i<c2; ++i)if(IsPointInPolygon(ch2[i], ch1) != 0) return false;for(int i=0; i<c1; ++i)for(int j=0; j<c2; ++j)if(SegmentProperIntersection(ch1[i], ch1[(i+1)%c1], ch2[j], ch2[(j+1)%c2])) return false;return true;
}int main() {int n, m; int t;scanf("%d%d", &n,&m);vector<Point> P1, P2;double x, y;int ww;for(int i = 0; i < n; i++) {scanf("%lf%lf", &x, &y);P1.push_back(Point(x, y));        }for(int i = 0;i < m; ++i) {scanf("%lf%lf", &x, &y);P2.push_back(Point(x, y));}if(P1.size()==0||P2.size()==0){printf("NO\n");return 0;}if(ConvexPolygonDisjoint(ConvexHull(P1), ConvexHull(P2)))printf("NO\n");elseprintf("YES\n");return 0;
}

其他

快读快输模板

有些题目可能会丧心病狂的卡常,这个时候我们可以使用快读快输这种方法优化我们程序的常数。

/*作者:Mangataint的快读快输
*/
#include<cstdio>inline int read()
{int X=0; bool flag=1; char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') flag=0; ch=getchar();}while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();}if(flag) return X;return ~(X-1);
}inline int write(int X)
{if(X<0) {putchar('-'); X=~(X-1);}int s[20],top=0;while(X) {s[++top]=X%10; X/=10;}if(!top) s[++top]=0;while(top) putchar(s[top--]+'0');
}
int main()
{while(1){int n = read();write(n);putchar('\n');}return 0;
}

_int128模板

能够存储128位2进制的数(算是微大数)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;inline void scan(__int128 &x)//输入
{x = 0;int f = 1;char ch;if((ch = getchar()) == '-') f = -f;else x = x*10 + ch-'0';while((ch = getchar()) >= '0' && ch <= '9')x = x*10 + ch-'0';x *= f;
}
inline void print(__int128 x) {if(x < 0) {x = -x;putchar('-');}if(x > 9)print(x/10);putchar(x % 10 + '0');
}int main()
{__int128 a,b,c;scan(a);scan(b);c = a+b;print(c);return 0;}

二分模板

二分的模板有很多,最好记住并使用一种二分模板就行,这是Mangata最爱的二分模板

/*作者:Mangata二分模板返回大于x的第一个位置
*/
#include<cstdio>
#include<algorithm>
#include<cstring>
#define N 1005
using namespace std;int a[N],n,q;int find(int l,int r,int key)//l为-1,r为数组长度
{while(l + 1 < r){int mid = l + r>>1;if(a[mid] <= key)l = mid;elser = mid;}return r;//返回大于Key的第一个位置
}int main()
{int k;scanf("%d%d",&n,&q);for(int i = 0; i < n; ++i)scanf("%d",&a[i]);for(int i = 0; i < q; ++i){scanf("%d",&k);printf("%d\n",find(-1,n,k));}
}

排序

快速排序

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);
}

归并排序

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];
}

归并排序求逆序对

#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;
}

JAVA高精度

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==belse if( a.compareTo(b) > 0 ) System.out.println("a > b"); //大整数a>belse if( a.compareTo(b) < 0 ) System.out.println("a < b"); //大整数a<bint n;BigInteger m;n=cin.nextInt(); //读入一个int;m=cin.nextBigInteger();//读入一个BigInteger;m=BigInteger.valueOf(n);//将n转换成大整数}
}

C++高精度

#include<iostream>
#include<string>
#include<cstring>
#include<cstdio>
using namespace std;
const int N = 1005;
struct bign
{int len,s[N];bign()  {  memset(s,0,sizeof(s));  len=1;  }bign(int num)  {  *this=num; }bign(char *num) { *this=num; }bign operator =(int num){char c[N];sprintf(c,"%d",num);*this=c;return *this;}bign operator =(const char *num){len=strlen(num);for (int i=0;i<len;i++) s[i]=num[len-1-i]-'0';return *this;}string str(){string res="";for (int i=0;i<len;i++) res=(char)(s[i]+'0')+res;return res;}void clean(){while (len>1&&!s[len-1]) len--;}bign operator +(const bign &b){bign c;    c.len=0;for (int i=0,g=0;g||i<len||i<b.len;i++){int x=g;if (i<len) x+=s[i];if (i<b.len) x+=b.s[i];c.s[c.len++]=x%10;g=x/10;}return c;}bign operator -(const bign &b){bign c;c.len=0;int x;     for (int i=0,g=0;i<len;i++){x=s[i]-g;if (i<b.len) x-=b.s[i];if (x>=0) g=0;else{          x+=10;g=1;};c.s[c.len++]=x;}c.clean();return c;}bign operator *(const bign &b){bign c;c.len=len+b.len;for (int i=0;i<len;i++) for (int j=0;j<b.len;j++) c.s[i+j]+=s[i]*b.s[j];for (int i=0;i<c.len-1;i++) { c.s[i+1]+=c.s[i]/10; c.s[i]%=10; }c.clean();return c;  }bool operator <(const bign &b){if (len!=b.len) return len<b.len;for (int i=len-1;i>=0;i--)if (s[i]!=b.s[i]) return s[i]<b.s[i];return false;}bign operator +=(const bign &b){*this=*this+b;return *this;}bign operator -=(const bign &b){*this=*this-b;return *this;}
};
istream& operator >>(istream &in,bign &x)
{string s;in>>s;x=s.c_str();return in;
}
ostream& operator <<(ostream &out,bign &x)
{out<<x.str();return out;
}
int main(){bign a,b,c;ios::sync_with_stdio(false);cin>>a>>b;
//    cout<<a<<endl;
//    cout<<b<<endl;c=a+b;cout<<c<<endl;return 0;
}

坐标离散化

例一

步骤

具体的离散化操作分为三部分
1. 排序
2. 去重(去掉相邻的重复元素)
3. 坐标离散化处理(最关键的!!!详情请参见代码)

code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;struct Node{int x,y;
};const int N = 505;
bool mp[N * 6][N * 6];
int dx[4] = {-1,1,0,0},dy[4] = {0,0,-1,1};
int X1[N * 6],Y1[N * 6],X2[N * 6],Y2[N * 6];
int W,H,n;
//对x1和x2进行离散化处理,并且返回离散后的宽度
int compress(int *x1,int *x2,int w) {vector<int> xs;//先将所有x坐标放进xs数组中 for(int i = 0; i < n; ++i) {for(int d = -1; d <= 1; ++d) {int tx1 = x1[i] + d;int tx2 = x2[i] + d;if(1 <= tx1 && tx1 <= w)  xs.push_back(tx1);if(1 <= tx2 && tx2 <= w)  xs.push_back(tx2);}}//1.排序 sort(xs.begin(),xs.end()); //2.去重    因为unique函数返回值是去掉相邻重复元素后的尾部的位置//         (他会把重复的元素添加到尾部,所以元素的个数还是没变) xs.erase((unique(xs.begin(),xs.end())),xs.end()); //3.离散化,将大空间的下标投射到小范围中 for(int i = 0; i < n; ++i) {x1[i] = find(xs.begin(),xs.end(),x1[i]) - xs.begin();//当然这里也可以自己写一个二分函数 x2[i] = find(xs.begin(),xs.end(),x2[i]) - xs.begin();//貌似更快 }return xs.size();//返回的是离散后的宽或者长
}
void bfs(int x,int y) {queue<Node> que;que.push({x,y});mp[x][y] = true;while(!que.empty()) {Node t = que.front();que.pop();for(int i = 0; i < 4; ++i) {int nx = t.x + dx[i];int ny = t.y + dy[i];if(nx >= 0 && nx < H && ny >= 0 && ny < W && (!mp[nx][ny])) {que.push({nx,ny});mp[nx][ny] = true;}}}
}int main()
{scanf("%d%d%d",&W,&H,&n);for(int i = 0;i < n; ++i) scanf("%d",&X1[i]);for(int i = 0;i < n; ++i)    scanf("%d",&X2[i]);for(int i = 0;i < n; ++i)    scanf("%d",&Y1[i]);for(int i = 0;i < n; ++i)    scanf("%d",&Y2[i]);W = compress(X1,X2,W);//列数 H = compress(Y1,Y2,H);//行数 //绘制地图 for(int i = 0; i < n; ++i) {for(int y = Y1[i]; y <= Y2[i]; ++y) {for(int x = X1[i]; x <= X2[i]; ++x) {mp[y][x] = true; }}}for(int y = 0; y < H; ++y) {for(int x = 0; x < W; ++x) {printf("%d%c",mp[y][x],x==W-1?'\n':' '); }}int ans = 0;for(int i = 0; i < H; ++i) {for(int j = 0; j < W; ++j) {if(!mp[i][j]) {ans++;bfs(i,j);}}}printf("%d\n",ans);return 0;/*
10 10 5
1 1 4 9 10
6 10 4 9 10
4 8 1 1 6
4 8 10 5 10
*/
}

To Be Continue……

MangataのACM模板相关推荐

  1. 我的所有优质博客全部开源啦(我自己原创的《ACM模板》《算法全家桶》《算法竞赛中的初等数论》 PDF免费下载)

    你好呀ヾ(≧▽≦*)o 我是繁凡さん 这两年来我写了很多长篇文章,主要涉及数据结构,算法,程序设计竞赛,数学,计算几何等方面的内容: <数据结构>C语言版(清华严蔚敏考研版) 全书知识梳理 ...

  2. ACM模板 | 学习笔记 树相关

    持续更新中qwq 咕咕咕 此次update是在我原先自己的博客园博客的基础上进行更新的(隔了两年该忘的不该忘的都忘完了qwq),顺便整理一下我的acm模板QAQ (我保证2021.3.1开学之前搞完! ...

  3. 线性求逆元模板_ZXBlog/ACM模板(C++).md at bb6f2522054d5370df79222461293721e8edede2 · cw1027/ZXBlog · GitHub...

    ACM模板(C++) 1.大数 加法,乘法模板 //题目链接 : http://poj.org/problem?id=2506 //题目大意 : 就是问你用2*1,1*2,2*2的砖拼成2*n的长方形 ...

  4. ACM模板(满注释模板!)

    转自:https://fanfansann.blog.csdn.net/article/details/105493218 目录 F o r e w o r d ForewordForeword T ...

  5. 『ACM』ACM模板合集

    写在前面: 第一年小白拿铜牌,第二年队友出走,加上疫情原因不能回校训练导致心底防线彻底崩盘,于是选择退役. 自从退役之后,一直想我打了那么久的ACM,什么也没留下觉得很难受,突然想到我打ACM的时候, ...

  6. 这是一份简易的ACM模板

    1.基础算法 __int128 typedef __int128 LL; inline __int128 read(){__int128 x=0,f=1;char ch=getchar();while ...

  7. 自备ACM模板 —— 数据结构篇

    文章目录 ST表 线段树 区间加.区间乘混合 主席树 主席树+差分 主席树查询区间前K项前缀和 带修主席树 树上主席树 带修树上主席树 主席树判断存在性问题 二分套主席树 区间修改+标记永久化+树状数 ...

  8. 前端笔试常考设计模式,操作系统,数据结构,ACM模板,经典算法,正则表达式,常用方法

    考试时允许使用草稿纸,请提前准备纸笔.考试过程中允许上厕所等短暂离开,但请控制离开时间 笔试得分60%一般通过,面试答对80%才能通过 合集:2023年最全前端面试题考点HTML5+CSS3+JS+V ...

  9. [工具使用]-利用latex管理创建自己的ACM模板

    https://www.cnblogs.com/31415926535x/p/11448017.html 从很早入坑ACM开始,便和各种算法的模板打着交道,虽然kaungbin的模板已经足够强大,但是 ...

最新文章

  1. Forrester:2011年Q2数据库审计与实时保护市场分析报告【更新】
  2. 不要一棍子打翻所有黑盒模型,其实可以让它们发挥作用
  3. 基于动态IP的Internet视频监控解决方案(作者:吴晓晖)
  4. 大文件分片上传前端框架_基于Node.js的大文件分片上传
  5. 302状态码_http状态码是什么?301 302 404的SEO应用场景
  6. 引导界面图标好大_游戏里那些图标和界面,原来是这么设计出来的?
  7. 大学计算机基础基础部分试题及答案,大学计算机基础试题及答案,等级考网络部分...
  8. iOS UIStepper使用方法的总结
  9. mvc:annotation-driven/与mvc:default-servlet-handler/之间的一个问题
  10. Wireless Network概念解析
  11. 内网漫游之SOCKS代理大结局
  12. oracle 索引问题梳理
  13. css 页面机械/数字电子 字体怎么实现?
  14. 软件评测师--第17小时 功能测试
  15. [转帖]AMD、英特尔为何争相走向胶水多核处理器?真相在此
  16. jetson nano 3(Archiconda安装)
  17. w7设置双显示器_win7系统设置双显示器多屏幕模式的操作方法
  18. 地级市空气污染、空气质量、PM2.5日度数据
  19. 辉芒微IO单片机FT60F010A-URT
  20. 枪火——古龙之视觉化

热门文章

  1. SVN安装后创建仓库、用户、上传代码
  2. JS实现定时弹出广告
  3. shell变量、函数和数组以及字符串的截取
  4. 如何在乌版图系统添加拼音输入法!
  5. PHP数据库扩展 - PDO操作
  6. Ubuntu的 g++ gcc版本升降级
  7. python-3高级特征
  8. 【转】IP地址、子网掩码、网络号、主机号、网络地址、主机地址以及ip段
  9. tableview 图片缓存
  10. [教程]centos卸载、安装mysql(源码编译安装方式)