板子

模板整理、部分知识点文章上传至 Github

最大公约数

ll Gcd(ll a, ll b) { return a % b == 0 ? b : Gcd(b, a % b); }

欧拉函数

//欧拉函数:求出小于等于n的  与n互质的个数,如果求多个数的欧拉值则要 筛法欧拉函数
using ll = long long;
ll Euler(ll n) {
    ll ans = n;
    for (int i = 2; i * i <= n; ++i) {
        if (n % i == 0) {
            ans = ans / i * (i - 1);
            while (n % i == 0) n /= i;
        }
    }
    if (n > 1) ans = ans / n * (n - 1);
    return ans;
}

日期计算-基姆拉尔森

int Day(int y, int m, int d) {//由日期计算周几
    if (m == 1 || m == 2)  m += 12, y -= 1;
    return (d + 2 * m + 3 * (m + 1) / 5 + y + y / 4 - y / 100 + y / 400 + 1) % 7;
}

快速幂

模条件下的快速幂

typedef long long ll;
const ll mod = 1e9 + 7;
ll qpow(ll a,ll b) {
	ll ans = 1; a %= mod;
	for (; b; a = a * a % mod,b >>= 1)
		if (b & 1)ans = ans * a % mod;
	return ans;
}

快速乘

算法讲解:Here

inline ll ksc(ll x, ll y, ll mod) {
    ll res = 0;
    for (; y; y >>= 1, x = (x << 1) % mod)
        if (y & 1) res = (res + x) % mod;
    return res;
}
//O(log n)
// O(1)快速乘(防爆long long)
typedef long long ll;
typedef unsigned long long ull;
typedef long double lb;
//代码压缩
inline ll ksc(ull x, ull y, ll p) {  
    return (x * y - (ull)((lb)x / p * y) * p + p) % p;
}

二分(返回第一个等于x的元素的下标)

int found(int a[],int left,int right,int x) {
	while (left < right) {
		int mid = (right + left) >> 1;
		if (a[mid] < x) left = mid + 1;
		else right = mid;
	}
	return left;
}

并查集

int f[maxn];
void init() { for (int i = 1; i <= n; ++i)f[i] = i; }
int find(int x) { return f[x] == x ? x : f[x] = find(f[x]); }
void merge(int x, int y) {
    x = find(x), y = find(y);
    if (x != y)  f[y] = x;
}
...

带权并查集

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

const int maxn = 1e6 + 10;

int n, m;//n个顶点 m条边 
int father[maxn]; //元素父亲节点 
int Size[maxn];//集合大小 
int maxs[maxn];//集合最大值 
int dist[maxn];//元素x到它所在集合根节点的距离 
int setNums = 0;//集合总数 

//初始化 
void init() {
    setNums = n;
    for (int i = 1; i <= n; i++) 
        father[i] = maxs[i] = i, Size[i] = 1, dist[i] = 0;
}

//查找 
int find(int x) {
    if (father[x] == x) return x;
    int y = father[x];
    father[x] = find(y);
    dist[x] += dist[y]; //x到根的距离 需要加上y到根的距离 
    return father[x];
}

//合并 
void join(int x, int y) {
    int a = find(x), b = find(y);
    if (a != b) {
        setNums--;
        father[a] = b;
        dist[a] = Size[b];
        Size[b] += Size[a];
        maxs[b] = max(maxs[a], maxs[b]);
    }
}

//查询集合总数量 
int findSetnum() {
    return setNums;
}

//查询x所在集合的大小(元素数量)
int findSize(int x) {
    return Size[find(x)];
}

//查询x所在集合中的最大值 
int findSetMax(int x) {
    return maxs[find(x)];
}

//查询x到它的集合的根 的距离
int findDist(int x) {
    return dist[x];
}

int main() {

    return 0;
}

链式前向星

struct Edge {
	int next, to;
	LL dis;
}edges[maxn];

void add_edge(int from, int to, LL dis) {
	num++;
	edges[num].next = head[from];
	edges[num].to = to;
	edges[num].dis = dis;
	head[from] = num;
}

for (int i = head[u]; i != 0; i = edges[i].next) {
  ...
}

邻接矩阵

struct Edge {
	int from, to, dist;
	//边的起点,终点,长度
	Edge(int u,int v,int d):from(u),to(v),dist(d){}
	//构造函数,用于边的初始化
};

struct HeapNode {
	int d, u;//将结点的d值与结点捆绑在一起形成结构体,当然也可以用pair<int,int>代替
	bool operator < (const HeapNode& rhs) const {
		return d > rhs.d;
		//当d>rhs.d为真时,优先级this<rhs.d成立,即d值小的优先级更大
	}
};

void init(int n) {//初始化
  num=0;//边在vector中的编号
  for (int i = 0; i < n; i++) G[i].clear();
  edges.clear();
  //个人习惯,为了计数都能从1开始,先塞一个无关紧要的元素
  edges.push_back(Edge(0, 0, 0));
  //但G[i]计数还是从0开始
}

void AddEdge(int from, int to, int dist) {
  edges.push_back(Edge(from, to, dist));
  //调用Edge结构体中的构造函数,生成一条边并加入到Edge中
  G[from].push_back(++num);
}

拓扑排序

int n, m, head[N], tot, deg[N], cnt;
vector<int>a;
struct node {
    int v, next;
}e[N << 1];

void add(int u, int v) {
    e[++tot].v = v;
    e[tot].next = head[u];
    head[u] = tot;
    deg[v]++;//入度
}

void topsort() {
    queue<int>q;
    for (int i = 1; i <= n; ++i)
        if (deg[i] == 0)q.push(i);
    while (q.size()) {
        int x = q.front(); q.pop();
        a[++cnt] = x;
        for (int i = head[x]; i; i = e[i].next) {
            int y = e[i].v;
            if (--deg[y] == 0)q.push(y);
        }
    }
}

int main() {
    cin >> n >> m;//点数、边数
    for (int i = 1; i <= m; ++i) {
        int x, y;//有向边
        cin >> x >> y;
        add(x, y);
    }
    topsort();
    for (int i = 1; i <= a.size(); ++i)
        cout << a[i] << " ";
    cout << endl;
}

最短路(Floyd)

//初始化
for (int i = 0;i < maxn ; i++) for (int j = 0;j< maxn ; j++) 
		d[i][j] = (i == j) ? 0 : INF;

for (int k = 0; k < n; k++)
  for (int i = 0; i < n; i++)
    for (int j = 0; j < n; j++)
      d[i][j] = min(d[i][j], d[i][k] + d[k][j]);

//传递闭包
for (int k = 0; k < n; k++)
  for (int i = 0; i < n; i++)
    for (int j = 0; j < n; j++)
        d[i][j] |= d[i][k] & d[k][j];

最短路(SPFA)

int cnt[maxn];
bool inq[maxn];
int d[maxn];

bool spfa(int s) {
    queue<int>Q;
    memset(inq, 0, sizeof(inq));//标记结点是否在队列中
    memset(cnt, 0, sizeof(cnt));
    for (int i = 0; i < n; i++) d[i] = INF;
    d[s] = 0; inq[s] = true;//标记结点s已在队列中
    Q.push(s);//入队
    while (!Q.empty()) {
        int u = Q.front(); Q.pop();
        inq[u] = false;//标记已不在队列
        for (int i = 0; i < G[u].size(); i++) {
            Edge& e = Edges[G[u][i]];//遍历以结点u为起点的有向边
            if (d[u]<INF && d[u] + e.dist<d[e.to]) {
                d[e.to] = d[u] + e.dist;//松弛
                p[e.to] = G[u][i];//记录父节点
                if (!inq[e.to]) {//只要不在队列中就可以入队
                    Q.push(e.to);
                    inq[e.to] = true;
                    if (++cnt[e.to] > n) return false;
                    //如果某个点迭代了超过n次,说明存在可以无限缩短的最短路,即负环
                }
            }
        }
    }
    return true;
}

最短路(朴素Dijkstra)

int dis[maxn];
bool vis[maxn];

void dijkstra() {
    // memset(vis,false);
    // memset(dis,inf);

    for (int i = 1; i <= n; i++)
        dis[i] = inf, vis[i] = 0, a[i][i] = 0;  //初始化各数组
    dis[s] = 0;
    vis[s] = true;               // s作为起点
    for (int i = 1; i < n; i++)  //重复n-1次
    {
        int x = -1;  //寻找的最小边点
        for (int j = 1; j <= n; j++)
            if (!vis[j] &&
                (x == 0 || dis[j] < dis[x]))  //寻找未访问过且离起点最近的点
                x = j;
        if (x == -1)
            break;
        vis[x] = 1;
        for (int y = 1; y <= n; y++)  //第一轮dis[y]中由起点可到达的点得到更新
            dis[y] = min(dis[y], dis[x] + a[x][y])
    }
}

最短路(堆优化Dijkstra)

写法一:

#include <bits/stdc++.h>
using namespace std;
const int N   = 1e4 + 10;
const int inf = 0x3f3f3f3f;
vector<pair<int, int>> e[N];
priority_queue<pair<int, int>> q;
int n, m, s;
int dist[N];
bool vis[N];
void dijkstra() {
    for (int i = 1; i <= n; ++i) dist[i] = inf, vis[0];
    dist[s] = 0;
    q.push(make_pair(0, s));
    while (q.size()) {
        int x = q.top().second; // 输出队头
        q.pop();
        if (vis[x]) continue; // 如果点x访问过,跳过,访问下一个队头
        vis[x] = true;
        for (int i = 0; i < e[x].size(); ++i) {
            int v = e[x][i].first, w = e[x][i].second;
            if (dist[v] > dist[x] + w) {
                dist[v] = dist[x] + w;
                q.push(make_pair(-dist[v], v));
            }
        }
    }
}
int main() {
    cin >> n >> m >> s;
    for (int i = 1, u, v, w; i <= m; ++i) {
        cin >> u >> v >> w;
        e[u].push_back({v, w});
        // e[v].push_back({u, w});
    }
    dijkstra();
    for (int i = 1; i <= n; ++i) cout << dist[i] << " ";
    cout << "\n";
    return 0;
}

写法二:

#include <cstdio>
#include <iostream>
#include <queue>
using namespace std;
typedef long long ll;
const int N = 1e4 + 10;  // 对应顶点的个数
const int M = 5e5 + 5;   //对应边的个数
const int inf = 2147483647;
priority_queue<pair<int, int>> q;
struct node {
    int nxt, v, w;
} edge[N];    //边取M,其余取N
bool vis[N];  //这里的标记数组与spfa的vis数组含义不同,这里标记是否入过队列
int head[N], d[N];
int k = 0;                       // tot 总边数
int n, m, s, e;                  // s作为起点,e作为终点
void add(int u, int v, int w) {  //邻接表存储图
    edge[++k].nxt = head[u];
    edge[k].v = v;
    edge[k].w = w;
    head[u] = k;
}

void dijkstra() {
    for (int i = 1; i <= n; ++i)  // 初始化 vis d数组
        d[i] = inf, vis[i] = 0;
    d[s] = 0;  // s作为起点
    q.push(make_pair(0, s));
    while (q.size()) {
        int x = q.top().second;  //输出队头
        q.pop();
        if (vis[x])
            continue;  //如果点x访问过,跳过,访问下一个队头
        vis[x] = true;
        for (int i = head[x]; i; i = edge[i].nxt) {
            int v = edge[i].v, w = edge[i].w;
            if (d[v] > d[x] + w) {  //松弛操作,更新距离
                d[v] = d[x] + w;
                //把更新的距离和点入队,这里距离取负变成小根堆
                q.push(make_pair(-d[v], v));
            }
        }
    }
}

int main() {
    // freopen("in.txt","r",stdin);
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    cin >> n >> m >> s;
    for (int i = 1; i <= m; ++i) {
        int u, v, w;
        cin >> u >> v >> w;
        // scanf("%d %d %d", &u, &v, &w);
        add(u, v, w);
        // add(v, u, w);  //无向图
    }
    s = 1;
    dijkstra();
    for (int i = 1; i <= n; i++)
        printf("%d ", d[i]);
    puts("");
    return 0;
}

最小生成树(Kruskal)

#include<bits/stdc++.h>
using namespace std;
struct rec { int x, y, z; }edge[500010];
int fa[100010], n, m, ans;

bool cmp(rec a, rec b) {
	return a.z < b.z;
}
int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }

int main() {
	//并查集初始化
	for (int i = 1; i <= n; ++i)fa[i] = i;
	cin >> n >> m;
	for (int i = 1; i <= n; ++i)
		cin >> edge[i].x >> edge[i].y >> edge[i].z;
	//按照边权排序
	sort(edge + 1, edge + 1 + n, cmp);
	//求最小生成树
	for (int i = 1; i <= m; ++i) {
		int x = find(edge[i].x);
		int y = find(edge[i].y);
		if (x == y) continue;
		fa[x] = y; //如果不在同一个集合,总权值更新,并且集合和并
		ans += edge[i].z;
	}
	cout << ans << endl;
}

最小生成树(Prim)

const int inf=2147483647;
int a[manx][manx], d[manx], n, m, ans;
bool vis[manx];
void prim(){
	for(int i=1;i<=n;i++) d[i]=inf,vis[i]=0,a[i][i]=0; //初始化各数组
	d[1]=0; //1作为起点
	for(int i=1;i<n;i++) //重复n-1次
	{
		int x=0;
		for(int j=1;j<=n;j++)
			if( !vis[j] && (x==0||d[j]<d[x]) ) //寻找未访问过且离集合S最近的点
				x=j;
		vis[x]=1;
		for(int y=1;y<=n;y++)  //第一轮d[y]中由起点可到达的点得到更新
			d[y]=min( d[y ], a[x][y]) //这里与Dij不同,因为Dij求的是两点间的距离,而这里是点到集合距离
		}
}

强连通分量(korasaju 算法)

int V;                     // 顶点数
vector<int> G[maxn];       // 图的邻接表表示
vector<int> rG[maxn];      // 反向图
vector<int> vs;            // 后序遍历顺序的顶点列表
bool book[maxn];           // 访问标记
int cmp[maxn];             // 所属强连通分量的拓补序
void dfs(const int& v) {
	book[v] = true;
	for (int i = 0; i < G[v].size(); ++i)
		if (!book[G[v][i]])dfs(G[v][i]);
	vs.push_back(v);
}
void rdfs(const int& v, const int& k) {
	book[v] = true; cmp[v] = k;
	for (int i = 0; i < rG[v].size(); ++i)
		if (!book[rG[v][i]])rdfs(rG[v][i], k);
}
//korasaju 算法核心:双重DFS标记
int scc() {
	ms(book, false); vs.clear();
	for (int v = 0; v < V; ++v)
		if (!book[v])dfs(v);
	ms(book, false); int k = 0;
	for (int i = vs.size() - 1; i >= 0; --i)
		if (!book[vs[i]])rdfs(vs[i], k++);
	return k;//k为连通分量的个数
}

强连通分量(Tarjan 算法)


线段树

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int maxn = 5e5 + 10;
struct Node {
    int l, r, val;
    int sum;
    int lmax, rmax, dat;
    //不能简单的对比对比左右子区间的dat和值(区间最大和值)来更新本节点的区间最大和值,还要对比右子树的rmax+左子树的lmax的和值
}node[maxn << 2];
int n, m;
int a[maxn];

void pushdown(int rt) {
    node[rt].sum = node[rt << 1].sum + node[rt << 1 | 1].sum;
    node[rt].lmax = max(node[rt << 1].lmax, node[rt << 1].sum + node[rt << 1 | 1].lmax);
    node[rt].rmax = max(node[rt << 1 | 1].rmax, node[rt << 1 | 1].sum + node[rt << 1].rmax);
    node[rt].dat = max(node[rt << 1].dat, node[rt << 1 | 1].dat);
    node[rt].dat = max(node[rt].dat, node[rt << 1].rmax + node[rt << 1 | 1].lmax);
}

//构建线段树,完成区间和,区间max,dat的更新
void build(int rt, int l, int r) {
    node[rt].l = l, node[rt].r = r;
    if (l == r) {
        node[rt].val = node[rt].lmax = node[rt].rmax = node[rt].dat = a[l];
        node[rt].sum = a[l];
        return;
    }
    int mid = (l + r) >> 1;
    build(rt << 1, l, mid); build(rt << 1 | 1, mid + 1, r);
   	pushdown(rt);
    return;
}

Node query(int rt, int l, int r) {
    if (l <= node[rt].l && node[rt].r <= r) {
        return node[rt];
    }
    Node t1, t2, ans;
    t1.dat = t1.sum = t1.lmax = t1.rmax = t2.dat = t2.sum = t2.lmax = t2.rmax = -inf;
    int mid = (node[rt].l + node[rt].r) >> 1;

    if (l <= mid) t1 = query(rt << 1, l, r), ans.sum += t1.sum;
    if (r > mid) t2 = query(rt << 1 | 1, l, r), ans.sum += t2.sum;

    ans.dat = max(max(t1.dat, t2.dat), t1.rmax + t2.lmax);
    ans.lmax = max(t1.lmax, t1.sum + t2.lmax);
    ans.rmax = max(t2.rmax, t2.sum + t1.rmax);
    if (l > mid) ans.lmax = max(ans.lmax, t2.lmax);
    if (r <= mid) ans.rmax = max(ans.rmax, t1.rmax);
    return ans;
}

void update(int rt, int pos, int val) {
    if (node[rt].l == pos && node[rt].l == node[rt].r) {
        node[rt].sum = node[rt].val = val;
        node[rt].lmax = node[rt].rmax = node[rt].dat = val;
        return;
    }
    int mid = (node[rt].l + node[rt].r) / 2;
    //cout<<pos<<" "<<rt<<" "<<mid<<endl;
    if (pos <= mid) update(rt << 1, pos, val);
    else update(rt << 1 | 1, pos, val);
    pushdown(rt);
}

int main() {
    //freopen("in.txt","r",stdin);
    ios::sync_with_stdio(false);
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> a[i];
    build(1, 1, n);
    int op, l, r;
    while (m--) {
        cin >> op >> l >> r;
        if (op == 1) {
            if (l > r) swap(l, r);
            cout << query(1, l, r).dat << endl;//根据需要输出不同的
        }
        else {
            update(1, l, r);
        }
    }
    return 0;
}

树状数组

int n;
int a[1005],tree[1005]; //原数组和树状数组

int lowbit(int x) return x&(-x); //取出x的最低位1

//修改
void updata(int i,int p){    //在i位置加上p
    while(i <= n){
        tree[i] += p;//更新受影响的tree[i]
        i += lowbit(i);
    }
}

//查询
int getsum(int i){//求区间1到i的和
    int res = 0;
    while(i > 0){
        res += tree[i];
        i -= lowbit(i);
    }
    return res;
}

高级操作:

求逆序对

二维树状数组:①单点修改,区间查询 ②区间修改,区间查询

二分图:匈牙利算法

int M, N;            //M, N分别表示左、右侧集合的元素数量
int Map[MAXM][MAXN]; //邻接矩阵存图
int p[MAXN];         //记录当前右侧元素所对应的左侧元素
bool vis[MAXN];      //记录右侧元素是否已被访问过
bool match(int i)
{
    for (int j = 1; j <= N; ++j)
        if (Map[i][j] && !vis[j]) //有边且未访问
        {
            vis[j] = true;                 //记录状态为访问过
            if (p[j] == 0 || match(p[j])) //如果暂无匹配,或者原来匹配的左侧元素可以找到新的匹配
            {
                p[j] = i;    //当前左侧元素成为当前右侧元素的新匹配
                return true; //返回匹配成功
            }
        }
    return false; //循环结束,仍未找到匹配,返回匹配失败
}
int Hungarian()
{
    int cnt = 0;
    for (int i = 1; i <= M; ++i)
    {
        memset(vis, 0, sizeof(vis)); //重置vis数组
        if (match(i))
            cnt++;
    }
    return cnt;
}

背包问题

背包问题讲解:https://www.cnblogs.com/RioTian/p/13490425.html

//01背包
//dp[i][j] = max(dp[i−1][j], dp[i−1][j−w[i]]+v[i]) // 前提:j >= w[i]
int dp[max_m + 1],v[max_n + 1],w[max_n + 1];
memset(dp,0xcf,sizeof dp); //-inf
dp[0] = 1;
for(int i = 1;i <= n;++i)
    for(int j = m;j >= v[i];--j) //倒序循环
        dp[j] = max(dp[j],dp[j - v[i]] + w[i]);
int ans = 0;
for(int j = 0;j <= m;++j)
    ans = max(ans,dp[j]);

//=================================//
//完全背包
//不装入第 i种物品,即dp[i−1][j],同01背包;
//只有当装入第 i 个物品时(由于物品无限个)所以此时不应该转移到dp[i−1][j−w[i]]而应该转移到dp[i][j−w[i]]
//dp[i][j] = max(dp[i−1][j], dp[i][j−w[i]]+v[i]) // 前提:j >= w[i]
int dp[max_m + 1],v[max_n + 1],w[max_n + 1];
memset(dp,0xcf,sizeof dp);// -inf
dp[0] = 1;
for(int i = 1; i <= n; ++i)
    for(int j = v[i]; j <= m; ++j)//正序循环
        dp[j] = max(dp[j] ,dp[j - v[i]] + w[i]);
int ans = 0;
for(int j = 0;j <= m;++j)
    ans = max(ans,dp[j]);

//=================================//
//多重背包

数位DP

LL dp[20][][];
//维数视情况决定,比如在【不要62】中,dp状态要区分有没有6
int a[20];
//给出数字范围在10^18以内可用,位数更多就扩大一下

LL dfs(int pos,bool lead,bool limit){
//pos当前枚举的位置,lead有无前导零,limit有无最高位限制
    LL ans = 0;
    if (pos == -1) return 1;
    if (!limit && !lead && dp[pos] != -1) return dp[pos];
  	//无前导零和最高位限制才能使用已储存的状态
    int end = limit ? a[pos] : 9;
    for (int i = 0; i <= end; i++) {
        if (不合法的情况(除0以外)) continue;
        if (!lead && (!i)) continue;
        //当前位为是零但不是前导零,也不合法,跳过(如果0不合法的话)
        ans += dfs(pos - 1, lead&&(!i), limit && i == end);
        //当前位是合法情况,继续搜
    }
    if (!limit&&!lead)  dp[pos] = ans;
    //没有前导零也没有最高位限制,说明本次统计结果是通用的
    return ans;
}

LL part(LL x){
    int len = 0; LL k = x;
    while (k) { a[len++] = k % 10; k /= 10; }
    memset(dp, -1, sizeof (dp));
    return dfs(len - 1, true, true);
  	//如果涉及到前导零,一般刚开始时都是默认有的
}

//注意l=0的时候不能用,要特判
LL result(LL l , LL r) {
	  return part(r) - part(l-1);
}

网络流:Edmonds-karp增广路

const int inf = 0x3f3f3f3f,N = 2010, M = 20010;
int head[N], ver[M], edge[M], Next[M], v[N], incf[N], pre[N];
int n, m, s, t, tot, maxflow;

void add(int x, int y, int z) {
	ver[++tot] = y, edge[tot] = z, Next[tot] = head[x], head[x] = tot;
	ver[++tot] = x, edge[tot] = 0, Next[tot] = head[y], head[y] = tot;
}

bool bfs() {
	memset(v, 0, sizeof v);
	queue<int>q;
	q.push(s), v[s] = 1;//压入源点
	incf[s] = inf;//增广路上各边的最小剩余容量
	while (q.size()) {
		int x = q.front(); q.pop();
		for (int i = head[x]; i; i = Next[i]) {
			if (edge[i]) {
				int y = ver[i];
				if (v[y])continue;
				incf[y] = min(incf[y], edge[y]);//更新最小容量
				pre[y] = i;//保存前驱,便于找到最长路的具体方案
				q.push(y), v[y] = 1;
				if (y == t)return true;//找到汇点直接返回true
			}
		}
	}
	return false;
}

void update() {
	int x = t;
	while (x != s) {
		int i = pre[x];
		edge[i] -= incf[t];
		edge[i ^ 1] += incf[t];//利用“成对存储”
		//num xor 1 , odd - 1 even + 1
		x = ver[i ^ 1];
	}
	maxflow += incf[t];
}

int main() {
	freopen("in.txt", "r", stdin);
	while (cin >> n >> m) {
		memset(head, 0, sizeof head);
		//源点、汇点、边数、最大流
		s = 1, t = n, tot = 1, maxflow = 0;
		for (int i = 1; i <= m; ++i) {
			int x, y, c; cin >> x >> y >> c;
			add(x, y, c);
		}
		//Edmonds-karp增广路算法的时间复杂度为 O(nm^2)
		//然而实际运用中则远远达不到这个上界,效率较高。
		//一般能处理10^3 ~ 10^4 规模的网络
		while (bfs()) update();//BFS搜索增广路
		cout << maxflow << endl;
	}
}

网络流:Dinic算法

const int inf = 0x3f3f3f3f, N = 2010, M = 20010;
//d[N] 当前弧优化
int head[N], ver[M], edge[M], Next[M], d[N], now[M];
int n, m, s, t, tot, maxflow;
queue<int>q;

void add(int x, int y, int z) {
    ver[++tot] = y, edge[tot] = z, Next[tot] = head[x], head[x] = tot;
    ver[++tot] = x, edge[tot] = 0, Next[tot] = head[y], head[y] = tot;
}

bool bfs() {//残量网络上构建分层图
    memset(d, 0, sizeof d);
    while (q.size())q.pop();
    q.push(s); d[s] = 1; now[s] = head[s];
    while (q.size()) {
        int x = q.front(); q.pop();
        for (int i = head[x]; i; i = Next[i]) {
            if (edge[i] && !d[ver[i]]) {
                q.push(ver[i]);
                now[ver[i]] = head[ver[i]];
                d[ver[i]] = d[x] + 1;
                if (ver[i == t])return true;
            }
        }
    }
    return false;
}

int dinic(int x, int flow) {//在当前分层图上增广
    if (x == t)return flow;
    int rest = flow, k, i;
    for (i = now[x]; i && rest; i = Next[i]) {
        if (edge[i] && d[ver[i]] == d[x] + 1) {
            //int y = ver[i];
            k = dinic(ver[i], min(rest, edge[i]));//递归
            if (!k) d[ver[i]] = 0;//剪枝,去掉增广完毕的店
            edge[i] -= k;
            edge[i ^ 1] += k;
            rest -= k;
        }
    }
    now[x] = i;//当前弧优化(避免重复遍历从x出发不可拓展的边)
    return flow - rest;
}

int main() {
    freopen("in.txt", "r", stdin);
    cin >> n >> m;
    cin >> s >> t;
    tot = 1;
    for (int i = 1; i <= m; ++i) {
        int x, y, c; cin >> x >> y >> c;
        add(x, y, c);
    }
    int flow = 0;
    while (bfs())
        while (flow = dinic(s, inf))maxflow += flow;
    cout << maxflow << endl;
}

另一种写法

const int N = 110, M = 20010, inf = 1e8;
int h[N], to[M], nxt[M], c[M], tot = 1;
int n, m, s, t;
int d[N], now[N];
queue<int> q;

inline void add(int a, int b, int w) {
    to[++tot] = b;
    c[tot] = w;
    nxt[tot] = h[a];
    h[a] = tot;
}

bool bfs() {
    memset(d, 0, sizeof d);
    d[s] = 1;
    now[s] = h[s];
    while (!q.empty()) q.pop();
    q.push(s);
    while (!q.empty()) {
        int x = q.front();
        q.pop();
        for (int i = h[x], v; v = to[i], i; i = nxt[i]) {
            if (c[i] && !d[v]) {
                d[v] = d[x] + 1;
                now[v] = h[v];
                if (v == t)
                    return true;
                q.push(v);
            }
        }
    }
    return false;
}
int find(int x, int flow) {
    if (x == t)
        return flow;
    int rest = flow, k = 0;
    for (int i = now[x], v; v = to[i], i && rest; i = nxt[i]) {
        if (c[i] && d[v] == d[x] + 1) {
            now[x] = i;
            k = find(v, min(rest, c[i]));
            if (!k)
                d[v] = 0;
            c[i] -= k;
            c[i ^ 1] += k;
            rest -= k;
        }
    }
    return flow - rest;
}

int dinic(int s, int t) {
    int maxflow = 0, flow = 0;
    while (bfs())
        while (flow = find(s, inf)) maxflow += flow;
    return maxflow;
}

int main() {
    cin >> n >> m;
    int u, v;
    while (cin >> u >> v) {
        if (u == -1 && v == -1)
            break;
        add(u, v, 1);
        add(v, u, 0);
    }
    s = 0, t = n + 1;
    for (int i = 1; i <= m; i++) add(s, i, 1), add(i, s, 0);
    for (int i = m + 1; i <= n; i++) add(i, t, 1), add(t, i, 0);
    cout << dinic(s, t) << endl;
    return 0;
}

LCA(倍增)

int num = 0;
int head[maxn], depth[maxn], lg[maxn], fathers[maxn][20];

struct Edge {
	int next, to;
}edges[2 * maxn];

void add_edge(int u, int v) {
	num++;
	edges[num].to = v;
	edges[num].next = head[u];
	head[u] = num;
}

void dfs(int u, int u_father) {
	depth[u] = depth[u_father] + 1;
	fathers[u][0] = u_father;
	for (int i = 1; (1 << i) <= depth[u]; i++)
		fathers[u][i] = fathers[fathers[u][i - 1]][i - 1];
	for (int i = head[u]; i; i = edges[i].next) {
		if (edges[i].to != u_father) dfs(edges[i].to, u);
	}
}

int LCA(int u, int v) {
	if (depth[u] < depth[v]) swap(u, v);
	while (depth[u] > depth[v])
		u = fathers[u][lg[depth[u] - depth[v]] - 1];
	if (u == v) return u;
	for (int k = lg[depth[u]] - 1; k >= 0; k--) {
		if (fathers[u][k] != fathers[v][k]) {
			u = fathers[u][k];
			v = fathers[v][k];
		}
	}
	return fathers[u][0];
}

int main(){
  dfs(1, 0);
  //常数优化
  for (int i = 1; i <= n; i++) {
    lg[i] = lg[i - 1] + (1 << lg[i - 1] == i);
  }
}

LCA(树剖)

int num = 0;
int head[maxn], depth[maxn], siz[maxn], dad[maxn], top[maxn];
//top[i]:节点i所在重链的链头

struct Edge {
	int next, to;
}edges[2 * maxn];

void add_edge(int u, int v) {
	num++;
	edges[num].to = v;
	edges[num].next = head[u];
	head[u] = num;
}

void getDepth(int now) {
	siz[now] = 1;
	depth[now] = depth[dad[now]] + 1;
	for (int i = head[now]; i != 0; i = edges[i].next) {
		int v = edges[i].to;
		if (v != dad[now]) {
			dad[v] = now;
			getDepth(v);
			siz[now] += siz[v];
		}
	}
}

void getLink(int x) {
	int t = 0;
	if (!top[x]) top[x] = x;
	for (int i = head[x]; i != 0; i = edges[i].next) {
		int v = edges[i].to;
		if (v != dad[x] && siz[v] > siz[t]) t = v;
	}
	if (t) {
		top[t] = top[x];
		getLink(t);
	}
	for (int i = head[x]; i != 0; i = edges[i].next) {
		int v = edges[i].to;
		if (v != dad[x] && t != v) getLink(v);
	}
}

int LCA(int u, int v) {
	while (top[u] != top[v]) {
		if (depth[top[u]] < depth[top[v]]) swap(u, v);
		u = dad[top[u]];
	}
	//直到u和v位于同一条重链上
	return (depth[u] < depth[v]) ? u : v;
	//深度更小的那一个就是公共祖先
}


int main()
{
	//	ios::sync_with_stdio(false);
	//	int t; cin >> t; while (t--) {
	int n, m;
	scanf("%d", &n);
	for (int i = 1; i <= n - 1; i++) {
		int u, v;
		scanf("%d%d", &u, &v);
		add_edge(u, v);
		add_edge(v, u);
	}
	getDepth(1);
	getLink(1);
	return 0;
}

康托展开&逆康托展开

【原理】

\[X = A[0] * (n-1)! + A[1] * (n-2)! + … + A[n-1] * 0!\\ (A[i]表示在位置i后比位置i上数小的数的个数) \]

【举例】

\[在 (1, 2, 3, 4, 5) 5个数的排列组合中,计算 (3, 4, 1, 5, 2) 的康托展开值\\ X = 2 * 4! + 2 * 3! + 0 * 2! + 1 * 1! + 0 * 0! = 61 \]

const int FAC[] = { 1,1,2,6,24,120,720,5040,40320,362880,3628800 };

int cantor(int* a) {//算出全排列对应的哈希值
    int x = 0;
    for (int i = 0; i < 9; i++) {
        int smaller = 0;
        for (int j = i + 1; j < 9; j++) {
            if (a[j] < a[i]) smaller++;
        }
        x += FAC[9 - i - 1] * smaller;
    }
    return x+1;
    //注意全排列数组a是从零开始的
}


void decantor(int x,int*ans) {//x哈希值,n数字个数,a算出的全排列
    x--;
    vector<int> v;
    for (int i = 1; i <= 9; i++) v.push_back(i);
    for (int i = 0; i < 9; i++) {
        int r;
        r = x / FAC[9 - i - 1];
        x = x % FAC[9 - i - 1];
        sort(v.begin(), v.end());
        ans[i] = v[r];
        v.erase(v.begin() + r);
    }
    //注意算出的全排列数组ans是从0开始的
}

凸包(Graham Scan 算法)

算法介绍:https://www.cnblogs.com/RioTian/p/13714244.html

模板:https://www.cnblogs.com/RioTian/p/13854555.html (单独记录)

Miller-Rabin 素数检验算法

详细讲解:Here

typedef long long ll;
typedef unsigned long long ull;
typedef long double lb;
inline ll ksc(ull x, ull y, ll p) {  // O(1)快速乘(防爆long long)
    return (x * y - (ull)((lb)x / p * y) * p + p) % p;
}

inline ll ksm(ll x, ll y, ll p) {  //快速幂
    ll res = 1;
    while (y) {
        if (y & 1) res = ksc(res, x, p);
        x = ksc(x, x, p);
        y >>= 1;
    }
    return res;
}

inline bool mr(ll x, ll p) {
    if (ksm(x, p - 1, p) != 1) return 0;  //费马小定理
    ll y = p - 1, z;
    while (!(y & 1)) {  //一定要是能化成平方的形式
        y >>= 1;
        z = ksm(x, y, p);                    //计算
        if (z != 1 && z != p - 1) return 0;  //不是质数
        if (z == p - 1) return 1;  //一定要为1,才能继续二次探测
    }
    return 1;
}

inline bool prime(ll x) {
    if (x < 2) return 0;
    if (x == 2 || x == 3 || x == 5 || x == 7 || x == 43) return 1;
    return mr(2, x) && mr(3, x) && mr(5, x) && mr(7, x) && mr(43, x);
}

树的直径

DFS版

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

const int maxn = 1e5 + 5;
int vis[maxn], book[maxn];
vector<int> g[maxn];
int node = 1;
int path[maxn];
int n, m;
int ans = 0;

void init() {
    for (int i = 1; i <= n; i++) {
        path[i] = -1;
        vis[i] = 0;
    }
}

void dfs(int x, int dis) {
    vis[x] = 1;
    for (int i = 0; i < g[x].size(); i++) {
        int vv = g[x][i];
        if (!vis[vv]) {
            path[vv] = x;
            if (dis + 1 > ans) {
                node = vv;
                ans = dis + 1;
            }
            dfs(vv, dis + 1);
        }
    }
    vis[x] = 0;
}


int main() {
    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
        int u, v;
        cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    dfs(1, 0);
    int s1 = node;
    ans = 0;
    init();
    dfs(node, 0);
    int s2 = node;
    int maxDist = ans;
    cout << s1 << " " << s2 << " " << maxDist;
    return 0;
}

BFS版

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

struct edge {
    int v, w;
    edge(int v, int w) {
        this->v = v;
        this->w = w;
    }
};
vector<edge> vec[10001];
int d[10001], ans;
bool vis[10001];
int node; // 记录第一次dfs最远的点
void bfs(int u) {
    queue<int> q;
    q.push(u);
    while (!q.empty()) {
        int x = q.front();
        vis[x] = 1;
        q.pop();
        for (int i = 0; i < (int)vec[x].size(); i++) {
            int y = vec[x][i].v;
            if (vis[y]) continue;
            d[y] = d[x] + vec[x][i].w;
            if (d[y] > ans) {
                ans = d[y];
                node = y;
            }
            q.push(y);
        }
    }
}
int main() {
    // freopen("test.txt","r",stdin);
    int u, v, w;
    while (scanf("%d%d%d", &u, &v, &w) == 3) {
        vec[u].push_back(edge(v, w));
        vec[v].push_back(edge(u, w));
    }
    memset(vis, 0, sizeof(vis));
    ans = 0;
    d[1] = 0;
    bfs(1);
    memset(vis, 0, sizeof(vis));
    ans = 0;
    d[node] = 0;
    bfs(node);
    printf("%d\n", ans);
    return 0;
}
posted @ 2020-07-25 19:45  RioTian  阅读(325)  评论(11编辑  收藏