=============图论=============
有向图的强连通分量
// 有向图的强连通分量
const int MAX_V = 1000;
int V; // vertex nums
vector<int> G[MAX_V]; // compression in adj graph
vector<int> rG[MAX_V]; // 图的转置图
vector<int> vs; // 后序遍历的定点列表
bool used[MAX_V]; // 访问标记
int cmp[MAX_V]; // 所属强连通分量的拓扑排序
void add_edge(int from, int to) {
G[from].push_back(to);
rG[to].push_back(from);
}
void dfs(int v) {
used[v] = true;
for (int i = 0; i < G[v].size(); ++i) {
if (!used[G[v][i]]) dfs(G[v][i]);
}
vs.push_back(v);
}
void rdfs(int v, int k) {
used[v] = true;
cmp[v] = k;
for (int i = 0; i < rG[v].size(); ++i) {
if (!used[rG[v][i]]) rdfs(rG[v][i], k);
}
}
// 返回强连通分量的个数
int scc() {
memset(used, 0, sizeof(used));
vs.clear();
for (int v = 0; v < V; ++v) {
if (!used[v]) dfs(v);
}
memset(used, 0, sizeof(used));
int k = 0;
//
for (int i = vs.size() - 1; i >= 0; --i) {
if (!used[vs[i]]) rdfs(vs[i], k++);
}
return k;
}
拓扑排序
/*
复杂度:O(V + E)
输入:
n 全局变量,表示点数
g 全局变量,g[i]表示从点 i 出去的边
输出:
返回对给定的图,是否可以拓扑排序
L 全局变量,拓扑排序的结果
*/
// 基于广度优先搜索的拓扑排序
const int maxn = 100000 + 5;
vector<int> g[maxn];
int du[maxn], n, m, L[maxn];
bool topsort() {
memset(du, 0, sizeof(du));
for (int i = 0; i < n; ++i)
for (int j = 0; j < g[i].size(); ++j)
du[g[i][j]]++;
int tot = 0;
queue<int> Q;
for (int i = 0; i < n; ++i)
if (!du[i]) Q.push(i);
while (!Q.empty()) {
int x = Q.front(); Q.pop();
L[tot++] = x;
for (int j = 0; j < g[x].size(); ++j) {
int t = g[x][j];
du[t]--;
if (!du[t]) Q.push(t);
}
}
if (tot == n) return true;
return false;
}
匈牙利算法
// 时间复杂度为 O(n4) 的匈牙利算法,可继续优化到 O(n3)
// 等我来优化之
int W[maxn][maxn], n;
int Lx[maxn], Ly[maxn]; // 顶标
int left[maxn]; // left[i]为右边第 i 个点的编号
bool S[maxn], T[maxn]; // S[i] 和 T[i] 为左/右第 i 个点是否已经标记
bool match(int i) {
S[i] = true;
for (int j = 1; j <= n; j++) if (Lx[i] + Ly[i] == W[i][j] && !T[j]) {
T[j] = true;
if (!left[j] || match(left[j])) {
left[j] = i;
return true;
}
}
return false;
}
void update() {
int a = 1 << 30;
for (int i = 1; i <= n; i++) if (S[i]) {
for (int j = 1; j <= n; j++) if (!T[j]) {
a = min(a, Lx[i] + Ly[j] - W[i][j]);
}
}
for (int i = 1; i <= n; i++) {
if (S[i]) Lx[i] -= a;
if (T[i]) Ly[i] += a;
}
}
void KM() {
for (int i = 1; i <= n; i++) {
left[i] = Lx[i] = Ly[i] = 0;
for (int j = 1; j <= n; j++)
Lx[i] = max(Lx[i], W[i][j]);
}
for (int i = 1; i <= n; i++) {
for (;;) {
for (int j = 1; j <= n; j++) S[j] = T[j] = 0;
if (match(i)) break;
else update();
}
}
}
最小割最大流
// EdmondsKarp
// 需要包含的库
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
// 需要定义的量
const int INF = 100000000;
const int maxn = 1005;
struct Edge {
int from, to, cap, flow;
Edge(int _from, int _to, int _cap, int _flow)
: from(_from), to(_to), cap(_cap), flow(_flow) {}
};
struct EdmondsKarp {
int n, m;
vector<Edge> edges; // 边数的两倍
vector<int> G[maxn]; // 邻接表,G[i][j]表示节点 i 的第 j 条变在 e 数组中的序号
int a[maxn]; // 起点到 i 的可改进量
int p[maxn]; // 最短路上 p 的入弧编号
void init(int n) {
for (int i = 0; i < n; ++i) G[i].clear();
edges.clear();
}
void AddEdge(int from, int to, int cap) {
edges.push_back(Edge(from, to, cap, 0));
edges.push_back(Edge(to, from, 0, 0)); // 反向弧
m = edges.size();
G[from].push_back(m - 2);
G[to].push_back(m - 1);
}
int Maxflow(int s, int t) {
int flow = 0;
for (;;) {
memset(a, 0, sizeof(a));
queue<int> Q; Q.push(s);
a[s] = INF;
while (!Q.empty()) {
int x = Q.front(); Q.pop();
for (int i = 0; i < G[x].size(); ++i) {
Edge &e = edges[G[x][i]];
if (!a[e.to] && e.cap > e.flow) {
p[e.to] = G[x][i];
a[e.to] = min(a[x], e.cap - e.flow);
Q.push(e.to);
}
}
if (a[t]) break;
}
if (!a[t]) break;
for (int u = t; u != s; u = edges[p[u]].from) {
edges[p[u]].flow += a[t];
edges[p[u] ^ 1].flow -= a[t];
}
flow += a[t];
}
return flow;
}
};
// Dinic
// 最近这些算法都很少有注释,原因是我还没真正弄清它们的本质
// 所以暂且不加入注释和讲解,以免丢人现眼
struct Dinic {
int n, m, s, t;
vector<Edge> edges;
vector<int> G[maxn];
bool vis[maxn];
int d[maxn];
int cur[maxn];
bool BFS() {
memset(vis, 0, sizeof(vis));
queue<int> Q;
Q.push(s);
d[s] = 0;
vis[s] = 1;
while (!Q.empty()) {
int x = Q.front(); Q.pop();
for (int i = 0; i < G[x].size(); i+) {
Edge &e = edges[G[x][i]];
if (!vis[e.to] && e.cap > e.flow) {
vis[e.to] = 1;
d[e.to] = d[x] + 1;
Q.push(e.to);
}
}
}
return vis[t];
}
int DFS(int x, int a) {
if (x == t || a == 0) return a;
int flow = 0, f;
for (int &i = cur[x]; i < G[x].size(); i++) {
Edge &e = edges[G[x][i]];
if (d[x] + 1 == d[e.to] && (f = DFS(e.to, min(a, e.cap - e.flow))) > 0) {
e.flow += f;
edges[G[x][i] ^ 1].flow -= f;
flow += f;
a -= f;
if (a == 0) break;
}
}
return flow;
}
int maxflow(int s, int t) {
this->s = s; this->t = t;
int flow = 0;
while (BFS()) {
memset(cur, 0, sizeof(cur));
flow += DFS(s, INF);
}
return flow;
}
};
最小费用最大流 MCMF
// 需要包含的库
#include <vector>
#include <queue>
using namespace std;
// 需要定义的量,此处暂且用这些数值代替,按照实际需求编写
const int maxn = 1005;
const int INF = 100000000;
struct Edge {
int from, to, cap, flow, cost;
Edge(int _from, int _to, int _cap, int _flow, int _cost)
: from(_from), to(_to), cap(_cap), flow(_flow), cost(_cost) {}
};
struct MCMF {
int n, m, s, t;
vector<Edge> edges;
vector<int> G[maxn];
int inq[maxn];
int d[maxn];
int p[maxn];
int a[maxn];
void init(int n) {
this->n = n;
for (int i = 0; i < n; ++i) G[i].clear();
edges.clear();
}
void AddEdge(int from, int to, int cap, int cost) {
edges.push_back(Edge(from, to, cap, 0, cost));
edges.push_back(Edge(to, from, 0, 0, -cost));
m = edges.size();
G[from].push_back(m - 2);
G[to].push_back(m - 1);
}
bool BellmanFord(int s, int t, int &flow, long long &cost) {
for (int i = 0; i < n; ++i) d[i] = INF;
memset(inq, 0, sizeof(inq));
d[s] = 0; inq[s] = 1; p[s] = 0; a[s] = INF;
queue<int> Q;
while (!Q.empty()) {
int u = Q.front(); Q.pop();
inq[u] = 0;
for (int i = 0; i < G[u].size(); ++i) {
Edge &e = edges[G[u][i]];
if (e.cap > e.flow && d[e.to] > d[u] + e.cost) {
d[e.to] = d[u] + e.cost;
p[e.to] = G[u][i];
a[e.to] = min(a[u], e.cap - e.flow);
if (!inq[e.to]) { Q.push(e.to); inq[e.to] = 1; }
}
}
}
if (d[t] == INF) return false;
flow += a[t];
cost += (long long)d[t] * (long long)a[t];
for (int u = t; u != s; u = edges[p[u]].from) {
edges[p[u]].flow += a[t];
edges[p[u] ^ 1].flow -= a[t];
}
return true;
}
int MincostMaxflow(int s, int t, long long &cost) {
int flow = 0; cost = 0;
while (BellmanFord(s, t, flow, cost));
return flow;
}
};
Dijkstra
struct Edge {
int from, to, dist;
Edge(int u, int v, int d) : from(u), to(v), dist(d) {}
};
struct HeapNode {
int d, u;
bool operator < (const HeapNode &rhs) const {
return d > rhs.d;
}
HeapNode(int dd = 0, int uu = 0) : d(dd), u(uu) {}
};
struct Dijkstra {
int n, m;
vector<Edge> edges;
vector<int> G[maxn];
bool done[maxn];
int d[maxn];
int p[maxn];
void init(int n) {
this->n = n;
for (int i = 0; i < n; ++i) G[i].clear();
edges.clear();
}
void AddEdge(int from, int to, int dist) {
edges.push_back(Edge(from, to, dist));
m = edges.size();
G[from].push_back(m - 1);
}
void dijkstra(int s) {
priority_queue<HeapNode> Q;
for (int i = 0; i < n; ++i) d[i] = INF;
d[s] = 0;
memset(done, 0, sizeof(done));
Q.push(HeapNode(0, s));
while (!Q.empty()) {
HeapNode x = Q.top(); Q.pop();
int u = x.u;
if (done[u]) continue;
for (int i = 0; i < G[u].size(); ++i) {
Edge &e = edges[G[u][i]];
if (d[e.to] > d[u] + e.dist) {
d[e.to] = d[u] + e.dist;
p[e.to] = G[u][i];
Q.push(HeapNode(d[e.to], e.to));
}
}
}
}
};
Bellman-Ford的SPFA改进算法
在一些代码中没有直接使用Bellman-Ford算法,主要原因是效率问题。于是出现这个SPFA改进的算法,维护一个队列存储点
bool bellman_ford(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;
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]];
if (d[u] < INF && d[e.to] > d[u] + e.dist) {
d[e.to] = d[u] + e.si;
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;
}
}
}
}
return true;
}
// 下列为一成品SPFA的封装
struct SPFA {
int n, m;
vector<Edge> edges;
vector<int> G[N];
bool vis[N];
long long d[N];
int p[N];
void init(int _n) {
n = _n;
}
void relief() {
for (int i = 0; i < n; ++i)
G[i].clear();
edges.clear();
}
void addedge(int from, int to, int spst) {
edges.push_back(Edge(from, to, spst));
m = edges.size();
G[from].push_back(m - 1);
}
void spfa(int s) {
queue<int> Q;
// 清空队列,所有元素均不再队列中
while (!Q.empty()) Q.pop();
// vis[] ~~ inq[]
for (int i = 0; i < n; ++i) {
d[i] = INF;
vis[i] = 0;
}
d[s] = 0;
vis[s] = 1;
Q.push(s);
while (!Q.empty()) {
int u = Q.front(); Q.pop();
vis[u] = 0;
for (int i = 0; i < G[u].size(); ++i) {
Edge &e = edges[G[u][i]];
if (d[e.to] > d[u] + e.spst) {
d[e.to] = d[u] + e.spst;
p[e.to] = G[u][i];
if (!vis[e.to]) {
vis[e.to] = 1;
Q.push(e.to);
}
}
}
}
}
};
Kruskal
TODO:将算法中的并查集改成这个模板中的并查集算法
// 把所有边排序,记第 i 小的边为 e[i] (1 <= i < m)
// 初始化 MST 为空
// 初始化连通分量,让每一个点自成一个独立的连通分量
// for (int i = 0; i < m; ++i)
// if (e[i].u 与 e[i].v 不在同一个连通分量) {
// 把边 e[i] 加入 MST
// 合并 e[i].u 和 e[i].v 所在的连通分量
// 上述伪代码中关键在于“连通分量的查询与合并”:需要知道两个点是否在一个连通分量,
// 还需要合并连通分量。
// 如下为使用并查集的 Kruskal 算法
// 第 i 条边的两个端点号和权值存在 u[i], v[i], w[i] 中
int cmp(const int i, const int j) { return w[i] < w[j]; } // 间接排序函数
int find(int x) { return p[x] == x ? x : p[x] = find(p[x]); } // 并查集查找
// 排序后第 i 小的边的序号存在 r[i] 中
int Kruskal()
{
int ans = 0;
for (int i = 0; i < n; ++i) p[i] = i; // 初始化并查集
for (int i = 0; i < m; ++i) r[i] = i; // 初始化边序号
sort(r, r + m, cmp);
for (int i = 0; i < m; ++i) {
int e = r[i];
int x = find(u[e]);
int y = find(v[e]);
if (x != y) {
ans += w[e];
p[x] = y;
}
}
return ans;
}
Prim
const int MAX_V = 100;
/* 輸入數據 */
// 表示兩點之間邊的權值
int cost[MAX_V][MAX_V];
// 從集合 X 出發的邊到每個頂點的最小權值
int mincost[MAX_V];
// 頂點是否包含在集合之中
bool used[MAX_V];
// 頂點之數目
int V;
int Prim() {
// 初始所有節點不在 X 中 —— used[i] = false
// 從集合 X 出發的邊到其他點的最小權值 —— mincost[i] = INF
for (int i = 0; i < V; ++i) {
mincost[i] = INF;
used[i] = false;
}
// 選取 0 點爲 X 集合的第一個節點
mincost[0] = 0;
int res = 0;
while (true) {
int v = -1;
// 從不屬於 X 的頂點中選取從 X 到其權值最小的頂點
for (int u = 0; u < V; ++u) {
// u 不屬於 X —— !used[u]。
if (!used[u] && (mincost[u] < mincost[v] || v == -1)) v = u;
}
if (v == -1) break;
// 將此點加入集合 X
used[v] = true;
// 總生成樹權值 res 累加
res += mincost[v];
// 更新與 X 集合鏈接的最小邊權值
for (int u = 0; u < V; ++u) {
mincost[u] = min(mincost[u], cost[v][u]);
}
}
return res;
}
============数据结构============
无根树转有根树
vector<int> G[maxn];
// read_tree用来读取一个图,此图可以看作是一个无根树
void read_tree()
{
int u, v;
scanf("%d", &n);
for (int i = 0; i < n - 1; ++i) {
scanf("%d%d", &u, &v);
G[u].push_back(v);
G[v].push_back(u);
}
}
// dfs用于转化成有根树
void dfs(int u, int fa)
{
int d = G[u].size();
for (int i = 0; i < d; ++i) {
int v = G[u][i];
if (v != fa)
dfs(v, p[v] = u);
}
}
树状数组
TODU:完善树状数组的代码及使用方式的注释
int lowbit(int x)
{
return x & -x;
}
int sum(int x)
{
int ret = 0;
while (x > 0) {
ret += C[x];
x -= lowbit(x);
}
return ret;
}
void add(int x, int d)
{
while (x <= n) {
C[x] += d;
x += lowbit(x);
}
}
并查集
// 并查集是一种用来管理元素分组情况的数据结构。并查集可以搞笑地进行如下操作。
// 不过需要注意并查集虽然可以进行合并操作,但是无法进行分割操作。
// *查询元素a和元素b是否属于同一组。
// *合并元素a和元素b所在的组。
// 并查集也是使用树形结构实现的。不过不是二叉树。每个元素对应一个节点,每个组
// 对应一个树。在并查集中,哪个节点是哪个节点的父亲以及树的形状等信息无需多加
// 关注,整体组成一个树形结构才是重要的。
// (1)初始化: 准备n个节点来表示n个元素,最开始没有边。
// (2)合并: 从一个组的根向另一个组的根连边,这样两棵树就变成了一棵树,也就
// 把两个组合并为一个组了。
// (3)查询: 为了查询两个节点是否属于同一组,需要沿着树向上走,来查询包含这
// 个元素的书的根是谁,如果两个节点有同一根,则属于同一组。
// ======================================================
int par[MAX_N]; // 父亲
int rank[MAX_N]; // 树的高度
// 初始化n个元素
void init(int n)
{
for (int i = 0; i < n; i++)
{
par[i] = i;
rank[i] = 0;
}
}
// 查询树的根
int find(int x)
{
if (par[x] == x)
return x;
else
return par[x] = find(par[x]);
}
// 合并x和y所属的集合
void unite(int x, int y)
{
x = find(x);
y = find(y);
if (x == y) return;
if (rank[x] < rank[y]) {
par[x] = y;
}
else {
par[y] = x;
if (rank[x] == rank[y])
rank[x]++;
}
}
// 判断x和y是否属于同一个集合
bool same(int x, int y)
{
return find(x) == find(y);
}
=============数论=============
素数筛选
/* ---------------------- Prime SCL ---------------------- */
// 运行后prime数组的prime[i]即为正整数i是否为素数的标识,true表示是素数
int prime[MAXN];
void getPrime()
{
memset(prime, true, sizeof(prime));
prime[0] = prime[1] = false;
for (int i = 2; i < MAXN; ++i) if (prime[i]) {
if (i > MAXN / i) continue;
for (int j = i * i; j < MAXN; j += i)
prime[j] = false;
}
}
/* ---------------------- Prime SCL ---------------------- */
GCD 和 LCM
int gcd(int a, int b)
{
return b == 0 ? a : gcd(b, a % b);
}
int lcm(int a, int b)
{
return a * b / gcd(a, b);
}
ExGCD
void exgcd(int a, int b, int &d, int &x, int &y)
{
if (!b) {
d = a;
x = 1;
y = 0;
}
else {
exgcd(b, a % b, d, y, x);
y -= x * (a / b);
}
}
取余模算术
a * b % n 的规模相当大,那么对 a 先取模再对 b 取模然后再进行乘法再取模
int mul_mod(int a, int b, int n)
{
a %= n;
b %= n;
return (int)((long long) a * b % n);
}
大整数的取余
n 为大整数的char 数组,传入 n 和 m 返回这个余数,m 是 int 类型,所以返回也是 int 类型
int large_mod(char *n, int m)
{
int len = strlen(n);
int ans = 0;
for (int i = 0; i <len; ++i)
ans= (int)(((long long) ans * 10 + n[i] - '0') % m);
return ans;
}
幂取模
输入正整数 a、n 和 m,输出 a ^ n mod m 的值。a,n,m <= 10 ^ 9
int pow_mod(int a, int n, int m)
{
if (n == 0) return 1;
int x = pow_mod(a, n / 2, m);
long long ans = (long long) x * x % m;
if (n % 2 == 1) ans = ans * a % m;
return (int) ans;
}
=============数学=============
矩阵类
const int MAXN = 1010;
const int MAXM = 1010;
struct Matrix {
int n, m;
int a[MAXN][MAXM];
void clear() {
n = m = 0;
memset(a, 0, sizeof(a));
}
Matrix operator +(const Matrix &b) const {
Matrix tmp;
tmp.n = n; tmp.m = m;
for (int i = 0; i < n; ++i)
for (int j = 0; j < m; ++j)
tmp.a[i][j] = a[i][j] + b.a[i][j];
return tmp;
}
Matrix operator -(const Matrix &b) const {
Matrix tmp;
tmp.n = n; tmp.m = m;
for (int i = 0; i < n; ++i)
for (int j = 0; j < m; ++j)
tmp.a[i][j] = a[i][j] - b.a[i][j];
return tmp;
}
Matrix operator *(const Matrix &b) const {
Matrix tmp;
tmp.clear();
tmp.n = n; tmp.m = b.m;
for (int i = 0; i < n; ++i)
for (int j = 0; j < m; ++j)
for (int k = 0; k < m; ++k)
tmp.a[i][j] += a[i][k] * b.a[k][j];
return tmp;
}
};
高斯消元
/*
复杂度:O(n^3)
输入:
a 方程组对应的矩阵
n 未知数个数
l, ans 存储解,l[]表示是否为自由元
输出:解空间的维数
*/
inline int solve(double a[][MAXN], bool l[], double ans[], const int &n) {
int res = 0, r = 0;
for (int i = 0; i < n; ++i)
l[i] = false;
for (int i = 0; i < n; ++i) {
for (int j = r; j < n; ++j) {
if (fabs(a[j][i]) > EPS) {
for (int k = i; k <= n; ++k)
swap(a[j][k], a[r][k]);
break;
}
}
if (fabs(a[r][i]) < EPS) {
++res;
continue;
}
for (int j = 0; j < n; ++j) {
if (j != r && fabs(a[j][i]) > EPS) {
double tmp = a[j][i] / a[r][i];
for (int k = i; k <= n; ++k)
a[j][k] -= tmp * a[r][k];
}
}
l[i] = true;
++r;
}
for (int i = 0; i < n; ++i) if (l[i]) {
for (int j = 0; j < n; ++j)
if (fabs(a[j][i]) > 0)
ans[i] = a[j][n] / a[j][i];
}
return res;
}
矩阵的逆
/*
复杂度:O(n^3)
输入:
A 原矩阵
C 逆矩阵
N 矩阵的阶数
*/
inline vector<double> operator *(vector<double> a, double b) {
int N = a.size();
vector<double> res(N, 0);
for (int i = 0; i < N; ++i)
res[i] = a[i] * b;
return res;
}
inline vector<double> operator -(vector<double> a, vector<double> b) {
int N = a.size();
vector<double> res(N, 0);
for (int i = 0; i < N; ++i)
res[i] = a[i] - b[i];
return res;
}
inline void inverse(vector<double> A[], vector<double> C[], int N) {
for (int i = 0; i < N; ++i) {
C[i] = vector<double>(N, 0);
}
for (int i = 0; i < N; ++i) {
C[i][i] = 1;
}
for (int i = 0; i < N; ++i) {
for (int j = 0; j < N; ++j) {
if (fabs(A[j][i]) > 0) {
swap(A[i], A[j]);
swap(C[i], C[j]);
break;
}
}
C[i] = C[i] * (1 / A[i][i]);
A[i] = A[i] * (1 / A[i][i]);
for (int j = 0; j < N; ++j)
if (j != i && fabs(A[j][i]) > 0) {
C[j] = C[j] - C[i] * A[j][i];
A[j] = A[j] - A[i] * A[j][i];
}
}
}