OJ-Problems-Source/.ACM-Templates/TXTs/图论模板.txt

2264 lines
74 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

==========图论模板 LCA=========
//LCA (Least Common Ancestors, 最近公共祖先)
//在线dfs + ST O(nlogn + q)
int dfso[N << 1], cnt; //欧拉序列, 即dfs序, 长度为2n-1, 下标从1开始
int pos[N]; //pos[i]表示点i在欧拉序列中第一次出现的位置
int dep[N << 1]; //欧拉序列对应的深度
int p[N << 1] = { -1}, dp[N << 1][20];
void initRMQ(int n) {
for (int i = 1; i <= n; i++) { p[i] = p[i - 1] + !(i & (i - 1)); dp[i][0] = i; }
for (int j = 1; j <= p[n]; j++) {
for (int i = 1; i + (1 << j) - 1 <= n; i++) {
dp[i][j] = dep[dp[i][j - 1]] < dep[dp[i + (1 << (j - 1))][j - 1]] ? dp[i][j - 1] : dp[i + (1 << (j - 1))][j - 1];
}
}
}
inline int queryRMQ(int l, int r) {
if (l > r) { swap(l, r); }
int k = p[r - l + 1];
return dep[dp[l][k]] <= dep[dp[r - (1 << k) + 1][k]] ? dp[l][k] : dp[r - (1 << k) + 1][k];
}
void dfs(int u, int p, int d) {
dfso[++cnt] = u; dep[cnt] = d; pos[u] = cnt;
for (int i = head[u]; ~i; i = nxt[i]) {
int v = to[i];
if (v != p) { dfs(v, u, d + 1); dfso[++cnt] = u; dep[cnt] = d; }
}
}
void initLCA(int rt, int n) {
cnt = 0; dfs(rt, rt, 0); initRMQ(2 * n - 1);
}
inline int queryLCA(int u, int v) { //查询u和v的LCA编号
return dfso[queryRMQ(pos[u], pos[v])];
}
//倍增法 O(nlogn + qlogn)
const int DEP = 20;
int fa[N][DEP]; //fa[i][j]表示结点i的第2^j个祖先
int dep[N]; //深度
void bfs(int rt) {
queue<int> que; que.push(rt);
dep[rt] = 0; fa[rt][0] = rt;
while (!que.empty()) {
int u = que.front(); que.pop();
for (int i = 1; i < DEP; i++) { fa[u][i] = fa[fa[u][i - 1]][i - 1]; }
for (int i = head[u]; ~i; i = nxt[i]) {
int v = to[i];
if (v != fa[u][0]) { dep[v] = dep[u] + 1; fa[v][0] = u; que.push(v); }
}
}
}
int queryLCA(int u, int v) {
if (dep[u] > dep[v]) { swap(u, v); }
for (int d = dep[v] - dep[u], i = 0; d; d >>= 1, i++) {
if (d & 1) { v = fa[v][i]; }
}
if (u == v) { return u; }
for (int i = DEP - 1; i >= 0; i--) {
if (fa[u][i] != fa[v][i]) { u = fa[u][i]; v = fa[v][i]; }
}
return fa[u][0];
}
//离线Tarjan O(n + q)
int n, fa[N], ancestor[N], ans[N];
bool vis[N];
int qhead[N], qto[M], qnxt[M], qtot;
void init() {
qtot = 0; memset(qhead, -1, sizeof(qhead));
memset(ancestor, 0, sizeof(ancestor)); memset(vis, 0, sizeof(vis));
for (int i = 0; i <= n; i++) { fa[i] = i; }
}
void qaddedge(int x, int y) {
qto[qtot] = y; qnxt[qtot] = qhead[x]; qhead[x] = qtot++;
qto[qtot] = x; qnxt[qtot] = qhead[y]; qhead[y] = qtot++;
}
int findfa(int n) { return n == fa[n] ? n : fa[n] = findfa(fa[n]); }
inline void unite(int x, int y) {
x = findfa(x); y = findfa(y);
if (x != y) { fa[y] = x; }
}
void Tarjan_LCA(int u) {
ancestor[u] = u; vis[u] = true;
for (int i = head[u]; ~i; i = nxt[i]) {
int v = to[i];
if (!vis[v]) { Tarjan_LCA(v); unite(u, v); ancestor[findfa(u)] = u; }
}
for (int i = qhead[u]; ~i; i = qnxt[i]) {
int v = qto[i];
if (vis[v]) { ans[i >> 1] = ancestor[findfa(v)]; }
}
}
==================图论-连通性==================
//图的割点、桥与双连通分量
//[点连通度与边连通度]
//在一个无向连通图中, 如果有一个顶点集合, 删除这个顶点集合,
//以及这个集合中所有顶点相关联的边以后, 原图变成多个连通块, 就称这个点集为割点集合
//一个图的点连通度的定义为, 最小割点集合中的顶点数
//类似的, 如果有一个边集合, 删除这个边集合以后, 原图变成多个连通块, 就称这个点集为割边集合
//一个图的边连通度的定义为, 最小割边集合中的边数
//[双连通图、割点与桥]
//如果一个无向连通图的点连通度大于1, 则称该图是点双连通的(point biconnected), 简称双连通或重连通
//一个图有割点, 当且仅当这个图的点连通度为1, 则割点集合的唯一元素被称为割点(cut point), 又叫关节点(articulation point)
//如果一个无向连通图的边连通度大于1, 则称该图是边双连通的(edge biconnected), 简称双连通或重连通
//一个图有桥, 当且仅当这个图的边连通度为1, 则割边集合的唯一元素被称为桥(bridge), 又叫关节边(articulation edge)
//可以看出, 点双连通与边双连通都可以简称为双连通, 它们之间是有着某种联系的, 下文中提到的双连通, 均既可指点双连通, 又可指边双连通
//[双连通分量]
//在图G的所有子图G'中, 如果G'是双连通的, 则称G'为双连通子图
//如果一个双连通子图G'它不是任何一个双连通子图的真子集, 则G'为极大双连通子图
//双连通分量(biconnected component), 或重连通分量, 就是图的极大双连通子图。特殊的, 点双连通分量又叫做块
//[求割点与桥]
//对图深度优先搜索, 定义DFS(u)为u在搜索树(以下简称为树)中被遍历到的次序号
//定义Low(u)为u或u的子树中能通过非父子边追溯到的最早的节点, 即DFS序号最小的节点
//根据定义, 则有: Low(u) = Min {DFS(u) DFS(v) (u,v)为后向边(返祖边) 等价于DFS(v) < DFS(u)且v不为u的父亲节点Low(v) (u,v)为树枝边(父子边)}
//一个顶点u是割点, 当且仅当满足(1)或(2)
//(1)u为树根, 且u有多于一个子树
//(2)u不为树根, 且满足存在(u,v)为树枝边(或称父子边, 即u为v在搜索树中的父亲), 使得DFS(u)<=Low(v)
//一条无向边(u,v)是桥, 当且仅当(u,v)为树枝边, 且满足DFS(u)<Low(v)
//[求双连通分量]
//对于点双连通分量, 实际上在求割点的过程中就能顺便把每个点双连通分量求出
//建立一个栈, 存储当前双连通分量, 在搜索图时, 每找到一条树枝边或后向边(非横叉边), 就把这条边加入栈中
//如果遇到某时满足DFS(u)<=Low(v), 说明u是一个割点, 同时把边从栈顶一个个取出,
//直到遇到了边(u,v), 取出的这些边与其关联的点, 组成一个点双连通分量
//割点可以属于多个点双连通分量, 其余点和每条边只属于且属于一个点双连通分量
//对于边双连通分量, 只需在求出所有的桥以后, 把桥边删除, 原图变成了多个连通块, 则每个连通块就是一个边双连通分量
//桥不属于任何一个边双连通分量, 其余的边和每个顶点都属于且只属于一个边双连通分量
//[构造双连通图]
//方法为首先求出所有的桥, 然后删除这些桥边, 剩下的每个连通块都是一个双连通子图
//把每个双连通子图收缩为一个顶点, 再把桥边加回来, 最后的这个图一定是一棵树, 边连通度为1
//统计出树中度为1的节点的个数, 即为叶节点的个数, 记为leaf
//则至少在树上添加(leaf + 1) / 2条边, 就能使树达到边二连通, 所以至少添加的边数就是(leaf + 1) / 2
//具体方法为, 首先把两个最近公共祖先最远的两个叶节点之间连接一条边, 这样可以把这两个点到祖先的路径上所有点收缩到一起,
//因为一个形成的环一定是双连通的。然后再找两个最近公共祖先最远的两个叶节点, 这样一对一对找完, 恰好是(leaf + 1) / 2次, 把所有点收缩到了一起
//有向图的强连通分量
//Kosaraju算法 O(V + E)
vector<int> e[N], re[N], pos;
bool vis[N];
int belong[N], num[N], scc, cnt; //所属分量, 分量点数, 分量个数
void dfs(int u) {
vis[u] = true;
for (int i = 0; i < (int)e[u].size(); i++) {
if (!vis[e[u][i]]) { dfs(e[u][i]); }
}
pos.push_back(u);
}
void rdfs(int u, int k) {
vis[u] = true; belong[u] = k; cnt++;
for (int i = 0; i < (int)re[u].size(); i++) {
if (!vis[re[u][i]]) { rdfs(re[u][i], k); }
}
}
void SCC(int n) {
memset(vis, 0, sizeof(vis)); pos.clear();
for (int i = 0; i < n; i++) { if (!vis[i]) { dfs(i); } }
memset(vis, 0, sizeof(vis)); memset(num, 0, sizeof(num)); scc = 0;
for (int i = (int)pos.size() - 1; i >= 0; i--) {
if (!vis[pos[i]]) { cnt = 0; rdfs(pos[i], ++scc); num[scc] = cnt; }
}
}
//Tarjan算法 O(V + E)
vector<int> e[N];
int low[N], dfn[N], index, belong[N], num[N], scc; //所属分量, 分量点数, 分量个数
stack<int> stk;
bool instack[N];
void Tarjan(int u) {
int v; low[u] = dfn[u] = ++index; stk.push(u); instack[u] = true;
for (int i = 0; i < (int)e[u].size(); i++) {
int v = e[u][i];
if (!dfn[v]) { Tarjan(v); low[u] = min(low[u], low[v]); }
else if (instack[v] && low[u] > dfn[v]) { low[u] = dfn[v]; }
}
if (low[u] == dfn[u]) {
scc++;
do {
v = stk.top(); stk.pop();
instack[v] = false; belong[v] = scc; num[scc]++;
} while (v != u);
}
}
void SCC(int n) {
memset(dfn, 0, sizeof(dfn)); memset(num, 0, sizeof(num));
memset(instack, 0, sizeof(instack)); index = scc = 0;
while (!stk.empty()) { stk.pop(); }
for (int i = 0; i < n; i++) { if (!dfn[i]) { Tarjan(i); } }
}
//无向图的割点和桥
//Tarjan算法 O(V + E)
int low[N], dfn[N], index, addblock[N], bridge;
bool instack[N], cut[N], ecut[M];
stack<int> stk;
void Tarjan(int u, int p) {
int son = 0; low[u] = dfn[u] = ++index;
stk.push(u); instack[u] = true;
for (int i = head[u]; ~i; i = nxt[i]) {
int v = to[i];
if (v == p) { continue; }
if (!dfn[v]) {
Tarjan(v, u); son++;
low[u] = min(low[u], low[v]);
if (u != p && low[v] >= dfn[u]) { //割点
cut[u] = true; addblock[u]++;
}
if (low[v] > dfn[u]) { //割边
bridge++; ecut[i] = ecut[i ^ 1] = true;
}
} else if (low[u] > dfn[v]) { low[u] = dfn[v]; }
}
if (u == p && son > 1) { cut[u] = true; }
if (u == p) { addblock[u] = son - 1; }
instack[u] = false; stk.pop();
}
void CUT(int n) {
memset(dfn, 0, sizeof(dfn)); memset(addblock, 0, sizeof(addblock));
memset(instack, 0, sizeof(instack)); memset(cut, 0, sizeof(cut)); memset(ecut, 0, sizeof(ecut));
while (!stk.empty()) { stk.pop(); } index = bridge = 0;
for (int i = 0; i < n; i++) { if (!dfn[i]) { Tarjan(i, i); } }
}
//无向图的边双连通分量
//求出所有的桥以后, 把桥边删除, 原图变成了多个连通块, 则每个连通块就是一个边双连通分量
//桥不属于任何一个边双连通分量, 其余的边和每个顶点都属于且只属于一个边双连通分量
//Tarjan算法 O(V + E)
int low[N], dfn[N], index, belong[N], bridge, block;
bool instack[N], ecut[M];
stack<int> stk;
void Tarjan(int u, int p) {
low[u] = dfn[u] = ++index; stk.push(u); instack[u] = true;
for (int i = head[u]; ~i; i = nxt[i]) {
int v = to[i];
if (v == p) { continue; }
if (!dfn[v]) {
Tarjan(v, u);
low[u] = min(low[u], low[v]);
if (low[v] > dfn[u]) { //割边
bridge++; ecut[i] = ecut[i ^ 1] = true;
}
} else if (low[u] > dfn[v]) { low[u] = dfn[v]; }
}
if (low[u] == dfn[u]) {
int v; block++;
do {
v = stk.top(); stk.pop(); instack[v] = false; belong[v] = block;
} while (u != v);
}
}
void EBCC(int n) {
memset(dfn, 0, sizeof(dfn)); memset(instack, 0, sizeof(instack)); memset(cut, 0, sizeof(cut));
while (!stk.empty()) { stk.pop(); } index = bridge = block = 0;
for (int i = 0; i < n; i++) { if (!dfn[i]) { Tarjan(i, -1); } }
}
//无向图的点双连通分量
//对于点双连通分量, 实际上在求割点的过程中就能顺便把每个点双连通分量求出
//建立一个栈, 存储当前双连通分量, 在搜索图时, 每找到一条树枝边或后向边(非横叉边), 就把这条边加入栈中
//如果遇到某时满足DFS(u)<=Low(v), 说明u是一个割点, 同时把边从栈顶一个个取出,
//直到遇到了边(u,v), 取出的这些边与其关联的点, 组成一个点双连通分量
//割点可以属于多个点双连通分量, 其余点和每条边只属于且属于一个点双连通分量
//Tarjan算法 O(V + E)
int low[N], dfn[N], index, belong[N], block; //所属分量, 分量点数, 分量个数
stack<int> stk;
bool instack[N];
void Tarjan(int u, int p) {
low[u] = dfn[u] = ++index;
stk.push(u); instack[u] = true;
for (int i = head[u]; ~i; i = nxt[i]) {
int v = to[i];
if (v == p) { continue; }
if (!dfn[v]) {
Tarjan(v, u);
low[u] = min(low[u], low[v]);
if (low[v] >= dfn[u]) { //u为割点
int vv; block++;
do {
vv = stk.top(); stk.pop(); instack[vv] = false;
belong[vv] = block;
} while (vv != v);
}
} else if (instack[v] && low[u] > dfn[v]) { low[u] = dfn[v]; }
}
}
void BCC(int n) {
memset(dfn, 0, sizeof(dfn)); memset(instack, 0, sizeof(instack));
while (!stk.empty()) { stk.pop(); } index = block = 0;
for (int i = 0; i < n; i++) { if (!dfn[i]) { Tarjan(i, -1); } }
}
//构造双连通图
//方法为首先求出所有的桥, 然后删除这些桥边, 剩下的每个连通块都是一个双连通子图
//把每个双连通子图收缩为一个顶点, 再把桥边加回来, 最后的这个图一定是一棵树, 边连通度为1
//统计出树中度为1的节点的个数, 即为叶节点的个数, 记为leaf
//则至少在树上添加(leaf + 1) / 2条边, 就能使树达到边二连通, 所以至少添加的边数就是(leaf + 1) / 2
//具体方法为, 首先把两个最近公共祖先最远的两个叶节点之间连接一条边, 这样可以把这两个点到祖先的路径上所有点收缩到一起,
//因为一个形成的环一定是双连通的。然后再找两个最近公共祖先最远的两个叶节点, 这样一对一对找完, 恰好是(leaf + 1) / 2次, 把所有点收缩到了一起
=============================图论-匹配============================
//图论点、边集和二分图的相关概念和性质
//点覆盖: 点覆盖集即一个点集, 使得所有边至少有一个端点在集合里
//边覆盖: 边覆盖集即一个边集, 使得所有点都与集合里的边邻接
//独立集: 独立集即一个点集, 集合中任两个结点不相邻, 则称V为独立集
//团: 团即一个点集, 集合中任两个结点相邻
//边独立集: 边独立集即一个边集, 满足边集中的任两边不邻接
//支配集: 支配集即一个点集, 使得所有其他点至少有一个相邻点在集合里
//边支配集: 边支配集即一个边集, 使得所有边至少有一条邻接边在集合里
//最小路径覆盖: 用尽量少的不相交简单路径覆盖有向无环图G的所有顶点, 即每个顶点严格属于一条路径, 路径的长度可能为0(单个点)
//匹配: 匹配是一个边集, 满足边集中的边两两不邻接
//在匹配中的点称为匹配点或饱和点;反之, 称为未匹配点或未饱和点
//交错轨(alternating path): 图的一条简单路径, 满足任意相邻的两条边, 一条在匹配内, 一条不在匹配内
//增广轨(augmenting path): 是一个始点与终点都为未匹配点的交错轨
//最大匹配(maximum matching): 具有最多边的匹配
//匹配数(matching number): 最大匹配的大小
//完美匹配(perfect matching): 匹配了所有点的匹配
//完备匹配(complete matching): 匹配了二分图较小集合二分图X, Y中小的那个的所有点的匹配
//增广轨定理: 一个匹配是最大匹配当且仅当没有增广轨
//所有匹配算法都是基于增广轨定理: 一个匹配是最大匹配当且仅当没有增广轨
//最大匹配数 = 最小覆盖数 = 左边匹配点 + 右边未匹配点
//最小边覆盖 = 图中点的个数 - 最大匹配数 = 独立数
//二分图最大匹配
//Hungary + dfs
//邻接矩阵 O(V^2)
int uN, vN, match[N]; //左右点的数目
bool g[N][N], check[N];
bool dfs(int u) {
for (int v = 0; v < vN; v++) {
if (g[u][v] && !check[v]) {
check[v] = true;
if (match[v] == -1 || dfs(match[v])) {
match[v] = u; match[u] = v; return true;
}
}
}
return false;
}
//邻接表 O(V*E)
int head[N], to[M], nxt[M], tot, uN, match[N];
bool check[N];
void init() { tot = 0; memset(head, -1, sizeof(head)); }
void addedge(int x, int y) { to[tot] = y; nxt[tot] = head[x]; head[x] = tot++; }
bool dfs(int u) {
for (int i = head[u]; ~i; i = nxt[i]) {
int v = to[i];
if (!check[v]) {
check[v] = true;
if (match[v] == -1 || dfs(match[v])) {
match[v] = u; match[u] = v; return true;
}
}
}
return false;
}
int Hungary() {
memset(match, -1, sizeof(match));
int res = 0;
for (int u = 0; u < uN; u++) {
if (match[u] == -1) {
memset(check, 0, sizeof(check));
if (dfs(u)) { res++; }
}
}
return res;
}
//Hungary + bfs + 邻接矩阵 O(V*E)
bool g[N][N];
int uN, vN, match[N], check[N], pre[N];
int Hungary() {
memset(match, -1, sizeof(match));
memset(check, -1, sizeof(check));
queue<int> que;
int res = 0;
for (int i = 0; i < uN; i++) {
if (match[i] == -1) {
while (!que.empty()) { que.pop(); }
que.push(i); pre[i] = -1;
bool flag = false;
while (!que.empty() && !flag) {
int u = que.front(); que.pop();
for (int v = 0; v < vN && !flag; v++) {
if (g[u][v] && check[v] != i) {
check[v] = i; que.push(match[v]);
if (~match[v]) { pre[match[v]] = u; }
else {
flag = true;
for (int a = u, b = v; ~a;) {
int t = match[a]; match[a] = b; match[b] = a; a = pre[a]; b = t;
}
}
}
}
}
if (~match[i]) { res++; }
}
}
return res;
}
//Hungary + bfs + 邻接表 O(V*E)
int head[N], to[M], nxt[M], tot, uN, match[N], check[N], pre[N];
void init() { tot = 0; memset(head, -1, sizeof(head)); }
void addedge(int x, int y) { to[tot] = y; nxt[tot] = head[x]; head[x] = tot++; }
int Hungary() {
memset(match, -1, sizeof(match));
memset(check, -1, sizeof(check));
queue<int> que;
int res = 0;
for (int i = 0; i < uN; i++) {
if (match[i] == -1) {
while (!que.empty()) { que.pop(); }
que.push(i); pre[i] = -1;
bool flag = false;
while (!que.empty() && !flag) {
int u = que.front(); que.pop();
for (int i = head[u]; ~i && !flag; i = nxt[i]) {
int v = to[i];
if (check[v] != i) {
check[v] = i; que.push(match[v]);
if (~match[v]) { pre[match[v]] = u; }
else {
flag = true;
for (int a = u, b = v; ~a;) {
int t = match[a]; match[a] = b; match[b] = a; a = pre[a]; b = t;
}
}
}
}
}
if (~match[i]) { res++; }
}
}
return res;
}
//Hopcroft-Karp + vector + O(sqrt(V)*E)
const int INF = 0x3f3f3f3f;
vector<int> g[N];
int uN, matchx[N], matchy[N], dx[N], dy[N], dis;
bool check[N];
bool SearchP() {
memset(dx, -1, sizeof(dx));
memset(dy, -1, sizeof(dy));
queue<int> que; dis = INF;
for (int i = 0; i < uN; i++) { if (matchx[i] == -1) { dx[i] = 0; que.push(i); } }
while (!que.empty()) {
int u = que.front(); que.pop();
if (dx[u] > dis) { break; }
for (size_t i = 0; i < g[u].size(); i++) {
int v = g[u][i];
if (dy[v] == -1) {
dy[v] = dx[u] + 1;
if (matchy[v] == -1) { dis = dy[v]; }
else { dx[matchy[v]] = dy[v] + 1; que.push(matchy[v]); }
}
}
}
return dis != INF;
}
bool dfs(int u) {
for (size_t i = 0; i < g[u].size(); i++) {
int v = g[u][i];
if (!check[v] && dy[v] == dx[u] + 1) {
check[v] = true;
if (~matchy[v] && dy[v] == dis) { continue; }
if (matchy[v] == -1 || dfs(matchy[v])) {
matchy[v] = u; matchx[u] = v; return true;
}
}
}
return false;
}
int HK() {
memset(matchx, -1, sizeof(matchx));
memset(matchy, -1, sizeof(matchy));
int res = 0;
while (SearchP()) {
memset(check, 0, sizeof(check));
for (int i = 0; i < uN; i++) {
if (matchx[i] == -1 && dfs(i)) { res++; }
}
}
return res;
}
//二分图最佳匹配
//Kuhn-Munkers + 邻接矩阵 O(uN^2*vN)
//若求最小权匹配,可将权值取相反数,结果取相反数 点的编号从0开始
const int INF = 0x3f3f3f3f;
int uN, vN, g[N][N];
int matchy[N], lx[N], ly[N]; //y中各点匹配状态, 可行顶标
int slack[N]; //松弛数组
bool visx[N], visy[N];
bool dfs(int u) {
visx[u] = true;
for (int v = 0; v < vN; v++) {
if (visy[v]) { continue; }
int tmp = lx[u] + ly[v] - g[u][v];
if (tmp == 0) {
visy[v] = true;
if (matchy[v] == -1 || dfs(matchy[v])) { matchy[v] = u; return true; }
} else if (slack[v] > tmp) { slack[v] = tmp; }
}
return false;
}
int KM() {
memset(matchy, -1, sizeof(matchy));
memset(ly, 0, sizeof(ly));
for (int i = 0; i < uN; i++) { lx[i] = *max_element(g[i], g[i] + vN); }
for (int u = 0; u < uN; u++) {
memset(slack, 0x3f, sizeof(slack));
for (;;) {
memset(visx, 0, sizeof(visx));
memset(visy, 0, sizeof(visy));
if (dfs(u)) { break; }
int d = INF;
for (int i = 0; i < vN; i++) { if (!visy[i] && d > slack[i]) { d = slack[i]; } }
for (int i = 0; i < uN; i++) { if (visx[i]) { lx[i] -= d; } }
for (int i = 0; i < vN; i++) { if (visy[i]) { ly[i] += d; } else { slack[i] -= d; } }
}
}
int res = 0;
for (int i = 0; i < vN; i++) { if (matchy[i] != -1) { res += g[matchy[i]][i]; } }
return res;
}
//二分图多重匹配
int uN, vN, g[N][N], match[N][N]; //match[i][0]为个数
int num[N]; //右边最大的匹配数
bool check[N];
bool dfs(int u) {
for (int v = 0; v < vN; v++) {
if (g[u][v] && !check[v]) {
check[v] = true;
if (match[v][0] < num[v]) { match[v][++match[v][0]] = u; return true; }
for (int i = 1; i <= num[0]; i++) {
if (dfs(match[v][i])) { match[v][i] = u; return true; }
}
}
}
return false;
}
int Hungary() {
int res = 0;
for (int i = 0; i < vN; i++) { match[i][0] = 0; }
for (int u = 0; u < uN; u++) {
memset(check, false, sizeof(check));
res += dfs(u);
}
return res;
}
//一般图最大匹配
//邻接矩阵 O(V^3)
int n, match[N];
bool g[N][N], check[N];
bool aug(int now) {
bool ret = false; check[now] = true;
for (int i = 0; i < n; i++) {
if (!check[i] && g[now][i]) {
if (match[i] == -1) { match[now] = i; match[i] = now; ret = true; }
else {
check[i] = true;
if (aug(match[i])) { match[now] = i, match[i] = now, ret = true; }
check[i] = false;
}
if (ret) { break; }
}
}
check[now] = false; return ret;
}
//邻接表 O(V*E)
int head[N], to[M], nxt[M], tot, match[N];
bool check[N];
void init() { tot = 0; memset(head, -1, sizeof(head)); }
void addedge(int x, int y) { to[tot] = y; nxt[tot] = head[x]; head[x] = tot++; }
bool aug(int now) {
bool ret = false; check[now] = true;
for (int i = head[now]; ~i; i = nxt[i]) {
int v = to[i];
if (!check[v]) {
if (match[v] == -1) { match[now] = v; match[v] = now; ret = true; }
else {
check[v] = true;
if (aug(match[v])) { match[now] = v; match[v] = now; ret = true; }
check[v] = false;
}
if (ret) { break; }
}
}
check[now] = false; return ret;
}
int graphMatch() {
memset(match, -1, sizeof(match));
memset(check, 0, sizeof(check));
for (int i = 0, j = n; i < n && j >= 2; i++) {
if (match[i] == -1 && aug(i)) { i = -1, j -= 2; }
}
int ret = 0;
for (int i = 0; i < n; i++) { ret += (match[i] != -1); }
return ret >> 1;
}
//带花树 + vector O(V^3) 点的编号从0开始
int n, match[N], fa[N], nxt[N], mark[N], vis[N], t;
vector<int> e[N]; queue<int> que;
int findfa(int n) { return n == fa[n] ? n : fa[n] = findfa(fa[n]); }
void unite(int x, int y) { fa[findfa(x)] = findfa(y); }
int lca(int x, int y) {
for (t++;; swap(x, y)) {
if (x == -1) { continue; }
if (vis[x = findfa(x)] == t) { return x; }
vis[x] = t; x = match[x] == -1 ? -1 : nxt[match[x]];
}
}
void group(int a, int p) {
for (int b, c; a != p; unite(a, b), unite(b, c), a = c) {
b = match[a]; c = nxt[b];
if (findfa(c) != p) { nxt[c] = b; }
if (mark[b] == 2) { mark[b] = 1; que.push(b); }
if (mark[c] == 2) { mark[c] = 1; que.push(c); }
}
}
void aug(int st) {
for (int i = 0; i < n; i++) { fa[i] = i; }
memset(nxt, -1, sizeof(nxt)); memset(vis, -1, sizeof(vis)); memset(mark, 0, sizeof(mark));
while (!que.empty()) { que.pop(); } que.push(st); mark[st] = 1;
while (match[st] == -1 && !que.empty()) {
int u = que.front(); que.pop();
for (int i = 0; i < (int)e[u].size(); i++) {
int v = e[u][i];
if (v == match[u] || findfa(u) == findfa(v) || mark[v] == 2) { continue; }
if (mark[v] == 1) {
int p = lca(u, v);
if (findfa(u) != p) { nxt[u] = v; }
if (findfa(v) != p) { nxt[v] = u; }
group(u, p); group(v, p);
} else if (match[v] == -1) {
nxt[v] = u;
for (int j = v, k, l; ~j; j = l) { k = nxt[j]; l = match[k]; match[j] = k; match[k] = j; }
break;
} else { nxt[v] = u; que.push(match[v]); mark[match[v]] = 1; mark[v] = 2; }
}
}
}
int solve(int n) {
memset(match, -1, sizeof(match)); t = 0; int ret = 0;
for (int i = 0; i < n; i++) { if (match[i] == -1) { aug(i); } }
for (int i = 0; i < n; i++) { ret += (match[i] > i); }
return ret;
}
//ural 1099
int main() {
int u, v;
scanf("%d", &n);
while (~scanf("%d%d", &u, &v)) { e[--u].push_back(--v); e[v].push_back(u); }
int ans = solve(n);
printf("%d\n", ans * 2);
for (int u = 0; u < n; u++) {
if (u < match[u]) { printf("%d %d\n", u + 1, match[u] + 1); }
}
}
============图论-生成树==========
//最小生成树
//Prim + 邻接矩阵 O(V^2)
typedef int wtype;
const wtype INF = 0x3f3f3f3f;
int n; wtype mp[N][N], cost[N];
bool vis[N];
wtype Prim(int src) {
memset(vis, 0, sizeof(vis)); memcpy(cost, mp[src], sizeof(mp[src]));
wtype ret = 0; vis[src] = true;
for (int j = 1; j < n; j++) {
int u = -1; wtype mn = INF;
for (int i = 0; i < n; i++) {
if (!vis[i] && mn > cost[i]) { mn = cost[i]; u = i; }
}
if (mn == INF) { return -1; } //原图不连通
vis[u] = true; ret += mn;
for (int v = 0; v < n; v++) {
if (!vis[v] && cost[v] > mp[u][v]) { cost[v] = mp[u][v]; }
}
}
return ret;
}
//Prim + priority_queue + 邻接表 O(ElogV)
typedef int wtype;
int head[N], to[M], nxt[M], tot; wtype len[M], cost[N];
bool vis[N];
void init() { tot = 0; memset(head, -1, sizeof(head)); }
void addedge(int x, int y, wtype z) { to[tot] = y; len[tot] = z; nxt[tot] = head[x]; head[x] = tot++; }
struct Node {
int v; wtype w;
bool operator<(const Node &r)const { return w > r.w; }
};
wtype Prim(int src) {
memset(cost, 0x3f, sizeof(cost)); memset(vis, 0, sizeof(vis));
wtype ret = 0;
priority_queue<Node> que; que.push((Node) {src, cost[src] = 0});
while (!que.empty()) {
int u = que.top().v; que.pop();
if (vis[u]) { continue; }
vis[u] = true; ret += cost[u];
for (int i = head[u]; ~i; i = nxt[i]) {
int v = to[i];
if (!vis[v] && cost[v] > len[i]) { cost[v] = len[i]; que.push((Node) {v, cost[v]}); }
}
}
return ret;
}
//Kruskal + 邻接表 O(ElogE)
typedef int wtype;
struct Edge {
int u, v; wtype w;
bool operator<(const Edge &r)const { return w < r.w; }
} edge[M];
int n, fa[N], tot; //加边前赋值为0
void addedge(int u, int v, wtype w) { edge[tot].u = u; edge[tot].v = v; edge[tot++].w = w; }
int findfa(int x) { return fa[x] == -1 ? x : fa[x] = findfa(fa[x]); }
wtype Kruskal() {
memset(fa, -1, sizeof(fa));
sort(edge, edge + tot);
int cnt = 0; wtype ret = 0;
for (int i = 0; i < tot; i++) {
int u = edge[i].u, v = edge[i].v, t1 = findfa(u), t2 = findfa(v); wtype w = edge[i].w;
if (t1 != t2) { ret += w; fa[t1] = t2; cnt++; }
if (cnt == n - 1) { break; }
}
if (cnt < n - 1) { return -1; } //不连通
return ret;
}
//次小生成树
//Prim + 邻接矩阵 O(V^2 + E)
//求最小生成树时, 用数组Mx[i][j]来表示MST中i到j最大边权
//求完后, 直接枚举所有不在MST中的边, 替换掉最大边权的边, 更新答案
typedef int wtype;
const wtype INF = 0x3f3f3f3f;
int n, pre[N];
wtype mp[N][N], cost[N], Mx[N][N]; //Mx[i][j]表示在最小生成树中从i到j的路径中的最大边权
bool vis[N], used[N][N];
wtype Prim() {
memset(Mx, 0, sizeof(Mx));
memset(pre, 0, sizeof(pre));
memset(vis, 0, sizeof(vis));
memset(used, 0, sizeof(used));
wtype ret = 0; vis[0] = true; pre[0] = -1;
for (int i = 1; i < n; i++) { cost[i] = mp[0][i]; }
for (int j = 1; j < n; j++) {
int u = -1; wtype mn = INF;
for (int j = 0; j < n; j++)
if (!vis[j] && mn > cost[j]) { mn = cost[j]; u = j; }
if (mn == INF) { return -1; } //原图不连通
vis[u] = true; ret += mn;
used[u][pre[u]] = used[pre[u]][u] = true;
for (int v = 0; v < n; v++) {
if (vis[v]) { Mx[v][u] = Mx[u][v] = max(Mx[v][pre[u]], cost[u]); }
if (!vis[v] && cost[v] > mp[u][v]) { cost[v] = mp[u][v]; pre[v] = u; }
}
}
return ret;
}
//Kruskal + 邻接表 O(VElogE)
typedef int wtype;
const wtype INF = 0x3f3f3f3f;
struct Edge {
int u, v; wtype w;
bool operator<(const Edge &r)const { return w < r.w; }
} edge[M];
int n, fa[N], path[N], tot; //加边前赋值为0
void addedge(int u, int v, wtype w) { edge[tot].u = u; edge[tot].v = v; edge[tot++].w = w; }
int findfa(int x) { return fa[x] == -1 ? x : fa[x] = findfa(fa[x]); }
wtype Kruskal() {
memset(fa, -1, sizeof(fa));
sort(edge, edge + tot);
int cnt = 0; wtype ret = 0;
for (int i = 0; i < tot; i++) {
int u = edge[i].u, v = edge[i].v, t1 = findfa(u), t2 = findfa(v); wtype w = edge[i].w;
if (t1 != t2) { ret += w; fa[t1] = t2; path[cnt++] = i; }
if (cnt == n - 1) { break; }
}
if (cnt < n - 1) { return -1; } //不连通
return ret;
}
wtype KruskalSec() {
wtype ret = INF;
for (int x = 0; x < n - 1; x++) {
memset(fa, -1, sizeof(fa));
int cnt = 0; wtype tmp = 0;
for (int i = 0; i < tot; i++) {
if (i != path[x]) {
int u = edge[i].u, v = edge[i].v, t1 = findfa(u), t2 = findfa(v); wtype w = edge[i].w;
if (t1 != t2) { tmp += w; fa[t1] = t2; cnt++; }
if (cnt == n - 1) { if (tmp < ret) { ret = tmp; } break; }
}
}
}
if (ret == INF) { return -1; } //不存在
return ret;
}
//最小树形图
//朱刘算法 O(VE)
typedef int wtype;
const int N = 1005;
const int M = 40005;
const wtype INF = 0x3f3f3f3f;
struct Edge { int u, v; wtype w; } edge[M];
int n, m, pre[N], id[N], vis[N];
wtype g[N][N], in[N];
wtype Zhuliu(int root) {
wtype res = 0; int u, v;
for (;;) {
memset(in, 0x3f, sizeof(in));
memset(id, -1, sizeof(id));
memset(vis, -1, sizeof(vis));
for (int i = 0; i < m; i++) {
if (edge[i].u != edge[i].v && edge[i].w < in[edge[i].v]) {
pre[edge[i].v] = edge[i].u; in[edge[i].v] = edge[i].w;
}
}
for (int i = 0; i < n; i++) {
if (i != root && in[i] == INF) { return -1; } //不存在最小树形图
}
int tn = 0; in[root] = 0;
for (int i = 0; i < n; i++) {
res += in[i]; v = i;
while (vis[v] != i && id[v] == -1 && v != root) {
vis[v] = i; v = pre[v];
}
if (v != root && id[v] == -1) {
for (int u = pre[v]; u != v ; u = pre[u]) { id[u] = tn; }
id[v] = tn++;
}
}
if (tn == 0) { break; } //没有有向环
for (int i = 0; i < n; i++) {
if (id[i] == -1) { id[i] = tn++; }
}
for (int i = 0; i < m;) {
v = edge[i].v; edge[i].u = id[edge[i].u]; edge[i].v = id[edge[i].v];
if (edge[i].u != edge[i].v) { edge[i++].w -= in[v]; }
else { swap(edge[i], edge[--m]); }
}
n = tn; root = id[root];
}
return res;
}
//POJ 3164
int main() {
int C = 0, T, u, v, w;
scanf("%d", &T);
while (++C <= T) {
memset(g, 0x3f, sizeof(g));
scanf("%d%d", &n, &m);
while (m--) {
scanf("%d%d%d", &u, &v, &w);
if (u == v) { continue; }
g[u][v] = min(g[u][v], w);
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (g[i][j] < INF) { edge[m].u = i; edge[m].v = j; edge[m++].w = g[i][j]; }
}
}
wtype ans = Zhuliu(0);
printf("Case #%d: ", C);
if (ans == -1) { puts("Possums!"); }
else { printf("%d\n", ans); }
}
}
//曼哈顿距离最小生成树
//Kruskal O(VlogV)
const int INF = 0x3f3f3f3f;
struct Point {
int x, y, id;
bool operator<(const Point &r)const { return x < r.x || (x == r.x && y < r.y); }
} p[N];
struct Edge { //有效边
int u, v, w;
bool operator<(const Edge &r)const { return w < r.w; }
} edge[N << 2];
struct BIT { //树状数组, 找y-x大于当前的, 但是y+x最小的
int min_val, pos;
void init() { min_val = INF; pos = -1; }
} bit[N];
int n, tot, fa[N];
int a[N], b[N];
void addedge(int u, int v, int w) { edge[tot].u = u; edge[tot].v = v; edge[tot++].w = w; }
int findfa(int x) { return fa[x] == -1 ? x : fa[x] = findfa(fa[x]); }
inline int cost(Point a, Point b) { return abs(a.x - b.x) + abs(a.y - b.y); }
inline int lowbit(int x) { return x & (-x); }
void update(int i, int val, int pos) {
for (; i > 0; i -= lowbit(i)) {
if (val < bit[i].min_val) { bit[i].min_val = val; bit[i].pos = pos; }
}
}
int query(int i, int m) { //查询[i, m]的最小值位置
int min_val = INF, pos = -1;
for (; i <= m; i += lowbit(i)) {
if (bit[i].min_val < min_val) { min_val = bit[i].min_val; pos = bit[i].pos; }
}
return pos;
}
void MMST() {
tot = 0;
for (int d = 0; d < 4; d++) { //4种坐标变换
if (d == 1 || d == 3) { for (int i = 0; i < n; i++) { swap(p[i].x, p[i].y); } }
else if (d == 2) { for (int i = 0; i < n; i++) { p[i].x = -p[i].x; } }
sort(p, p + n);
for (int i = 0; i < n; i++) { a[i] = b[i] = p[i].y - p[i].x; }
sort(b, b + n);
int m = unique(b, b + n) - b;
for (int i = 1; i <= m; i++) { bit[i].init(); }
for (int i = n - 1 ; i >= 0; i--) {
int pos = lower_bound(b, b + m, a[i]) - b + 1, ans = query(pos, m);
if (ans != -1) { addedge(p[i].id, p[ans].id, cost(p[i], p[ans])); }
update(pos, p[i].x + p[i].y, i);
}
}
}
int Kruskal() {
MMST();
memset(fa, -1, sizeof(fa));
sort(edge, edge + tot);
int ret = 0;
for (int i = 0, k = 0; i < tot; i++) {
int u = edge[i].u, v = edge[i].v, t1 = findfa(u), t2 = findfa(v);
if (t1 != t2) {
fa[t1] = t2; ret += edge[i].w;
if (++k == n - 1) { return ret; }
}
}
}
//POJ3241 求曼哈顿最小生成树上第k大的边
int Kruskal(int k) {
MMST(n, p);
memset(fa, -1, sizeof(fa));
sort(edge, edge + tot);
for (int i = 0; i < tot; i++) {
int u = edge[i].u, v = edge[i].v, t1 = findfa(u), t2 = findfa(v);
if (t1 != t2) {
fa[t1] = t2;
if (--k == 0) { return edge[i].w; }
}
}
}
//生成树计数
//Matrix-Tree定理(Kirchhoff 矩阵-树定理)
//1、G的度数矩阵D[G]是一个n*n的矩阵, 并且满足: 当i ≠ j时,dij = 0; 当i = j时, dij等于vi的度数
//2、G的邻接矩阵A[G]也是一个n*n的矩阵, 并且满足: 如果vi vj之间有边直接相连, 则aij = 1, 否则为0
//我们定义G的Kirchhoff矩阵(也称为拉普拉斯算子)C[G]为C[G] = D[G] - A[G], 则Matrix-Tree定理可以
//描述为: G的所有不同的生成树的个数等于其Kirchhoff矩阵C[G]任何一个n - 1阶主子式的行列式的绝对值
//所谓n - 1阶主子式, 就是对于r(1 ≤ r ≤ n), 将C[G]的第r行、第r列同时去掉后得到的新矩阵, 用Cr[G]表示。
============图论-网络流=========
//最大流
//SAP + 邻接矩阵 O(V^2*E) 点的编号默认从0开始
typedef long long ftype;
int n, dis[N], pre[N], cur[N], gap[N];
ftype cap[N][N];
ftype SAP(int src, int sink, int n) {
memset(dis, 0, sizeof(dis));
memset(cur, 0, sizeof(cur));
memset(gap, 0, sizeof(gap));
int u = src; ftype mxflow = 0, aug = -1; pre[src] = src; gap[0] = n;
while (dis[src] < n) {
loop:
for (int v = cur[u]; v < n; ++v) {
if (cap[u][v] > 0 && dis[u] == dis[v] + 1) {
if (aug == -1 || aug > cap[u][v]) { aug = cap[u][v]; }
pre[v] = u; u = cur[u] = v;
if (v == sink) {
for (u = pre[u]; v != src; v = u, u = pre[u]) {
cap[u][v] -= aug; cap[v][u] += aug;
}
mxflow += aug; aug = -1;
}
goto loop;
}
}
int mndis = n - 1;
for (int v = 0; v < n; v++) {
if (cap[u][v] > 0 && mndis > dis[v]) { cur[u] = v; mndis = dis[v]; }
}
if (--gap[dis[u]] == 0) { break; }
dis[u] = mndis + 1; gap[dis[u]]++; u = pre[u];
}
return mxflow;
}
//ISAP + 邻接表 O(V^2*E)
typedef long long ftype;
const ftype INF = 0x3f3f3f3f;
int head[N], to[M], nxt[M], tot, dis[N], pre[N], cur[N], gap[N];
ftype cap[M];
inline void init() { tot = 0; memset(head, -1, sizeof(head)); }
inline void addedge(int x, int y, ftype w, ftype rw = 0) {
to[tot] = y; cap[tot] = w; nxt[tot] = head[x]; head[x] = tot++;
to[tot] = x; cap[tot] = rw; nxt[tot] = head[y]; head[y] = tot++;
}
ftype ISAP(int src, int sink, int n) {
memset(dis, 0, sizeof(dis));
memset(gap, 0, sizeof(gap));
memcpy(cur, head, sizeof(head));
int u = src, v; ftype mxflow = 0; pre[u] = -1; gap[0] = n;
while (dis[src] < n) {
if (u == sink) {
ftype mndis = INF;
for (int i = pre[u]; ~i; i = pre[to[i ^ 1]]) {
if (mndis > cap[i]) { mndis = cap[i]; }
}
for (int i = pre[u]; ~i; i = pre[to[i ^ 1]]) {
cap[i] -= mndis; cap[i ^ 1] += mndis;
}
u = src; mxflow += mndis;
continue;
}
bool flag = false;
for (int i = cur[u]; ~i; i = nxt[i]) {
v = to[i];
if (cap[i] > 0 && dis[v] + 1 == dis[u]) {
flag = true; cur[u] = pre[v] = i; break;
}
}
if (flag) { u = v; continue; }
int mndis = n;
for (int i = head[u]; ~i; i = nxt[i]) {
if (cap[i] > 0 && dis[to[i]] < mndis) { mndis = dis[to[i]]; cur[u] = i; }
}
if (--gap[dis[u]] == 0) { return mxflow; }
dis[u] = mndis + 1; gap[dis[u]]++;
if (u != src) { u = to[pre[u] ^ 1]; }
}
return mxflow;
}
//ISAP + bfs标号 + queue + 邻接表 O(V^2*E)
typedef long long ftype;
const ftype INF = 0x3f3f3f3f;
int n, head[N], to[M], nxt[M], tot, dis[N], pre[N], cur[N], gap[N];
ftype cap[M];
inline void init() { tot = 0; memset(head, -1, sizeof(head)); }
inline void addedge(int x, int y, ftype w, ftype rw = 0) {
to[tot] = y; cap[tot] = w; nxt[tot] = head[x]; head[x] = tot++;
to[tot] = x; cap[tot] = rw; nxt[tot] = head[y]; head[y] = tot++;
}
void bfs(int sink) {
memset(dis, -1, sizeof(dis));
memset(gap, 0, sizeof(gap));
dis[sink] = 0; gap[0] = 1;
queue<int> que; que.push(sink);
while (!que.empty()) {
int u = que.front(); que.pop();
for (int i = head[u], v; ~i; i = nxt[i]) {
v = to[i];
if (~dis[v]) { continue; }
dis[v] = dis[u] + 1; gap[dis[v]]++; que.push(v);
}
}
}
ftype ISAP(int src, int sink, int n) {
bfs(sink);
memcpy(cur, head, sizeof(head));
int u = pre[src] = src, v, i; ftype mxflow = 0;
while (dis[sink] < n) {
if (u == sink) {
ftype mndis = INF; int inser;
for (i = src; i != sink; i = to[cur[i]]) {
if (mndis > cap[cur[i]]) { mndis = cap[cur[i]]; inser = i; }
}
for (i = src; i != sink; i = to[cur[i]]) {
cap[cur[i]] -= mndis; cap[cur[i] ^ 1] += mndis;
}
mxflow += mndis; u = inser;
}
for (i = cur[u]; ~i; i = nxt[i]) {
v = to[i];
if (dis[v] + 1 == dis[u] && cap[i] > 0) { break; }
}
if (~i) { cur[u] = i; pre[to[i]] = u; u = to[i]; }
else {
if (--gap[dis[u]] == 0) { break; }
int mndis = n;
for (i = head[u]; ~i; i = nxt[i]) {
if (cap[i] > 0 && mndis > dis[to[i]]) { cur[u] = i; mndis = dis[to[i]]; }
}
dis[u] = mndis + 1; gap[dis[u]]++; u = pre[u];
}
}
return mxflow;
}
//Dinic O(V^2*E)
typedef long long ftype;
const ftype INF = 0x3f3f3f3f;
int head[N], to[M], nxt[M], tot, dis[N], cur[N], src, sink;
ftype cap[M];
inline void init() { tot = 0; memset(head, -1, sizeof(head)); }
inline void addedge(int x, int y, ftype w, ftype rw = 0) {
to[tot] = y; cap[tot] = w; nxt[tot] = head[x]; head[x] = tot++;
to[tot] = x; cap[tot] = rw; nxt[tot] = head[y]; head[y] = tot++;
}
bool bfs() {
memset(dis, 0, sizeof(dis)); dis[src] = 1;
queue<int> que; que.push(src);
while (!que.empty()) {
int u = que.front(); que.pop();
for (int i = head[u], v; ~i; i = nxt[i]) {
v = to[i];
if (cap[i] > 0 && !dis[v]) { dis[v] = dis[u] + 1; que.push(v); }
}
}
return dis[sink];
}
ftype dfs(int u, ftype delta) {
if (u == sink || delta == 0) { return delta; }
ftype ret = 0;
for (int &i = cur[u], v; delta && ~i; i = nxt[i]) {
v = to[i];
if (cap[i] > 0 && dis[v] == dis[u] + 1) {
ftype aug = dfs(v, min(cap[i], delta));
if (!aug) { continue; }
cap[i] -= aug; cap[i ^ 1] += aug; delta -= aug; ret += aug;
if (!delta) { break; }
}
}
return ret;
}
ftype Dinic() {
ftype ret = 0;
while (bfs()) {
memcpy(cur, head, sizeof(head));
ret += dfs(src, INF);
}
return ret;
}
//HLPP Highest Label Preflow Push O(V^3)
typedef long long ftype;
struct Edge { int v; ftype c; } edge[M];
vector<int> e[N];
int n, m, src, sink, c[N << 1], d[N], done[N];
ftype w[N];
bool vis[N];
void init(int _n, int _src, int _sink) {
n = _n; m = 0; src = _src; sink = _sink;
for (int i = 0; i <= n; ++i) { e[i].clear(); }
}
inline void addEdge(int x, int y, ftype w, ftype rw = 0) {
edge[m].v = y; edge[m].c = w; e[x].push_back(m++);
edge[m].v = x; edge[m].c = rw; e[y].push_back(m++);
}
void bfs() {
memset(c, 0, sizeof(c));
fill(d, d + n, n + 1);
queue<int> que; que.push(sink);
c[n + 1] = n - 1; d[src] = n; d[sink] = 0;
while (!que.empty()) {
int u = que.front(); que.pop();
c[n + 1]++; c[d[u]]++;
for (int i = 0, v; i < (int)e[u].size(); i++) {
v = edge[e[u][i]].v; ftype c = edge[e[u][i] ^ 1].c;
if (d[v] == n + 1 && c > 0) { d[v] = d[u] + 1; que.push(v); }
}
}
}
int HLPP(int n) {
memset(w, 0, sizeof(w));
memset(done, 0, sizeof(done));
memset(vis, 0, sizeof(vis));
bfs();
int todo = -1;
vector<queue<int>> que(n << 1);
vis[src] = vis[sink] = true;
for (int i = 0, v; i < (int)e[src].size(); i++) {
Edge &arc = edge[e[src][i]], &cra = edge[e[src][i] ^ 1]; v = arc.v;
w[v] += arc.c; cra.c += arc.c; arc.c = 0;
if (!vis[v]) { vis[v] = true; que[d[v]].push(v); todo = max(todo, d[v]); }
}
while (todo >= 0) {
if (que[todo].empty()) { todo--; continue; }
int u = que[todo].front(); que[todo].pop();
vis[u] = false;
while (done[u] < (int)e[u].size()) {
Edge &arc = edge[e[u][done[u]]]; int v = arc.v;
if (d[u] == d[v] + 1 && arc.c > 0) {
Edge &cra = edge[e[u][done[u]] ^ 1]; ftype f = min(w[u], arc.c);
w[u] -= f; w[v] += f; arc.c -= f; cra.c += f;
if (!vis[v]) { vis[v] = true; que[d[v]].push(v); }
if (w[u] == 0) { break; }
}
done[u]++;
}
if (w[u] > 0) {
int du = d[u];
c[d[u]]--; d[u] = n << 1;
for (int i = 0, v; i < (int)e[u].size(); i++) {
Edge &arc = edge[e[u][i]]; v = arc.v;
if (d[u] > d[v] + 1 && arc.c > 0) { d[u] = d[v] + 1; done[u] = i; }
}
c[d[u]]++;
if (c[du] == 0) {
for (int i = 0; i < n; ++i) {
if (d[i] > du && d[i] < n + 1) { c[d[i]]--; c[n + 1]++; d[i] = n + 1; }
}
}
vis[u] = true; que[d[u]].push(u); todo = d[u];
}
}
return w[sink];
}
//有上下界的网络流
//无源汇上下界最大流
//增加超级源汇点, 边的容量变为原弧的上界减去下界的差, 记录每个点的入流下界和-出流下界和
//当下界和大于0时连一条超级源点到这个点的边, 容量为这个下界和
//当下界和小于0时连一条这个点到超级汇点的边, 容量为下界和的绝对值
//求一遍最大流, 如果源的出度满流的话, 即存在最大流, 否则则不存在最大流
//有源汇上下界最大流
//从汇连一条容量INF的边到源, 将有源汇转化为无源汇
//判断是否满流后, 去掉超级源汇及其边(head[]删除即可, 调用最大流算法时, 点的数量要包括超级源汇)
//再跑一遍原源到原汇的最大流, 输出即可
//有源汇上下界最小流
//增加超级源汇点, 求一遍超级源到超级汇的最大流
//从原汇点连一条容量INF的边到原源点, 再求一遍超级源到超级汇的最大流
//当且仅当超级源的出度满流时有可行解, 解为原汇点到原源点的反向弧
////最小费用最大流 MCMF O(V*E*f)
//最小费用流: 若dis[sink]为负则继续增广
//最小费用最大流: 若dis[sink]不为INF则继续增广
//求最大费用只需取相反数, 结果取相反数即可
typedef long long ftype;
const ftype INF = 0x3f3f3f3f;
int head[N], to[M], nxt[M], tot, cur[N];
ftype cap[M], cost[M], flow[N], dis[N], mncost, mxflow;
bool vis[N];
inline void init() { tot = 0; memset(head, -1, sizeof(head)); }
inline void addedge(int x, int y, ftype w, ftype c) {
to[tot] = y; cap[tot] = w; cost[tot] = c; nxt[tot] = head[x]; head[x] = tot++;
to[tot] = x; cap[tot] = 0; cost[tot] = -c; nxt[tot] = head[y]; head[y] = tot++;
}
bool SPFA(int src, int sink) {
memset(dis, 0x3f, sizeof(dis));
memset(vis, 0, sizeof(vis));
dis[src] = 0; cur[src] = -1; flow[src] = INF;
queue<int> que; que.push(src);
while (!que.empty()) {
int u = que.front(); que.pop();
vis[u] = false;
for (int i = head[u], v; ~i; i = nxt[i]) {
v = to[i];
if (cap[i] > 0 && dis[v] > dis[u] + cost[i]) {
dis[v] = dis[u] + cost[i]; flow[v] = min(flow[u], cap[i]); cur[v] = i;
if (!vis[v]) { vis[v] = true; que.push(v); }
}
}
}
if (dis[sink] == INF) { return false; }
mxflow += flow[sink]; mncost += flow[sink] * dis[sink];
for (int i = cur[sink]; ~i; i = cur[to[i ^ 1]]) {
cap[i] -= flow[sink]; cap[i ^ 1] += flow[sink];
}
return true;
}
ftype MCMF(int src, int sink) {
mxflow = mncost = 0;
while (SPFA(src, sink));
return mncost;
}
===========图论-应用==========
//拓扑排序 O(V^2)
//queue->普通判断用, priority_queue->字典序
//邻接矩阵
int n, mp[N][N], in[N], ret[N];
int topoSort() {
queue<int> que; int k = 0; bool flag = false;
for (int i = 0; i < n; i++) { if (in[i] == 0) { que.push(i); } }
while (!que.empty()) {
if (que.size() > 1) { flag = true; }
int u = que.front(); que.pop(); ret[k++] = u;
for (int v = 0; v < n; v++) {
if (mp[u][v] && --in[v] == 0) { que.push(v); }
}
}
return k < n ? -1 : flag ? 0 : 1; //有环, 不唯一, 唯一
}
//邻接表
int head[N], to[M], nxt[M], tot, n, in[N], ret[N];
void init() { tot = 0; memset(head, -1, sizeof(head)); }
void addedge(int x, int y) { to[tot] = y; nxt[tot] = head[x]; head[x] = tot++; }
bool topoSort() {
priority_queue<int> que; int k = 0;
for (int i = 0; i < n; i++) { if (in[i] == 0) { que.push(i); } }
while (!que.empty()) {
int u = que.top(); que.pop(); ret[k++] = u;
for (int i = head[u]; ~i; i = nxt[i]) {
if (--in[to[i]] == 0) { que.push(to[i]); }
}
}
return k == n;
}
//染色判断二分图
bool col[N];
bool Color(int u) {
for (int i = head[u]; ~i; i = nxt[i]) {
int v = to[i];
if (!col[v]) {
col[v] = !col[u];
if (!Color(v)) { return false; }
} else if (col[v] == col[u]) { return false; }
}
return true;
}
//欧拉回路: 每条边只经过一次, 要求回到起点
//欧拉路径: 每条边只经过一次, 不要求回到起点
//欧拉路径的判断:
//无向图: 连通(不考虑度为0的点), 每个顶点度数都为偶数或者有且仅有两个点的度数不为偶数
//有向图: 基图连通(把边当成无向边, 同样不考虑度为0的点), 每个顶点出度等于入度
//或者有且仅有一个点的出度比入度多1, 有且仅有一个点的出度比入度少1, 其余出度等于入度
//混合图: 如果存在欧拉回路, 一点存在欧拉路径了。否则如果有且仅有两个点的(出度 - 入度)是奇数,
//那么给这个两个点加边, 判断是否存在欧拉回路
//欧拉回路判断:
//无向图: 连通(不考虑度为0的点), 每个顶点度数都为偶数
//有向图: 基图连通(同样不考虑度为0的点), 每个顶点出度等于入度
//混合图: 基图连通(不考虑度为0的点), 然后借助网络流判定
//首先给原图中的每条无向边随便指定一个方向, 将原图改为有向图G'
//设D[i]为G'中(点i的出度 - 点i的入度), 若存在D[i]为奇数, 或者图不连通, 则无解
//若初始D值都是偶数, 则将G'改装成网络: 设立源点S和汇点T
//对于每个D[i] > 0的点i, 连边<S, i>, 容量为D[i] / 2; 对于每个D[j] < 0的点j, 连边<j, T>, 容量为-D[j] / 2
//G'中的每条边在网络中仍保留, 容量为1, 求这个网络的最大流, 若S引出的所有边均满流, 则原混合图是欧拉图
//将网络中所有流量为1的不与S或T关联的边在G'中改变方向,形成的新图G''一定是有向欧拉图
//欧拉回路 + 邻接矩阵 O(N^2)
//求欧拉路径/回路经过的点 支持自环和重边
int n, mp[N][N], path[N], cnt;
void dfsu(int u) {
for (int v = n - 1; v >= 0; v--) {
while (mp[u][v]) { mp[u][v]--; mp[v][u]--; dfsu(v); }
}
path[cnt++] = u;
}
void dfsd(int u) {
for (int v = n - 1; v >= 0; v--) {
while (mp[u][v]) { mp[u][v]--; dfsd(v); }
}
path[cnt++] = u;
}
//无向图 SGU101
int head[N], to[M], nxt[M], tot, deg[N], path[M], cnt;
bool vis[M];
void init() {
tot = 0; cnt = 0;
memset(head, -1, sizeof(head)); memset(vis, 0, sizeof(vis)); memset(deg, 0, sizeof(deg));
}
void addedge(int x, int y) {
to[tot] = y; nxt[tot] = head[x]; head[x] = tot++;
to[tot] = x; nxt[tot] = head[y]; head[y] = tot++;
}
void dfs(int u) {
for (int &i = head[u]; ~i;) {
if (!vis[i]) { vis[i] = vis[i ^ 1] = true; int t = i; dfs(to[i]); path[cnt++] = t; }
else { i = nxt[i]; }
}
}
int main() {
int n, u, v;
while (~scanf("%d", &n)) {
init();
for (int i = 0; i < n; i++) {
scanf("%d%d", &u, &v); addedge(u, v); deg[u]++; deg[v]++;
}
int s = -1, cnto = 0;
for (int i = 0; i <= 6; i++) {
if (s == -1 && deg[i] > 0) { s = i; }
if (deg[i] & 1) { cnto++; s = i; }
}
if (cnto != 0 && cnto != 2) { puts("No solution"); continue; }
dfs(s);
if (cnt != n) { puts("No solution"); continue; }
for (int i = 0; i < cnt; i++) {
printf("%d %c\n", (path[i] >> 1) + 1, path[i] & 1 ? '+' : '-');
}
}
}
//有向图 POJ2337
//给出n个小写字母组成的单词, 要求将n个单词连接起来, 输出字典序最小的解
int head[N], to[M], nxt[M], tot, in[N], out[N], path[N], cnt;
bool vis[M];
string str[N];
void init() { tot = 0; memset(head, -1, sizeof(head)); }
void addedge(int x, int y) {
to[tot] = y; nxt[tot] = head[x]; head[x] = tot++;
}
void dfs(int u) {
for (int &i = head[u]; ~i;) {
if (!vis[i]) { vis[i] = true; int t = i; dfs(to[i]); path[cnt++] = n - t - 1; }
else { i = nxt[i]; }
}
}
int main() {
int T, n;
char s[25];
scanf("%d", &T);
while (T--) {
init(); cnt = 0;
memset(vis, 0, sizeof(vis));
memset(in, 0, sizeof(in));
memset(out, 0, sizeof(out));
scanf("%d", &n);
for (int i = 0; i < n; i++) { scanf("%s", s); str[i] = s; }
sort(str, str + n);
int s = 26;
for (int i = n - 1; i >= 0; i--) { //字典序大的先加入
int u = str[i][0] - 'a', v = str[i][str[i].size() - 1] - 'a';
addedge(u, v); out[u]++; in[v]++;
s = min(s, min(u, v));
}
int cnt1 = 0, cnt2 = 0;
for (int i = 0; i < 26; i++) {
if (out[i] - in[i] == 1) { cnt1++; s = i; } //如果有一个出度比入度大1的点, 就从这个点出发, 否则从最小的点出发
else if (out[i] - in[i] == -1) { cnt2++; }
else if (out[i] - in[i] != 0) { cnt1 = 3; }
}
if (!((cnt1 == 0 && cnt2 == 0) || (cnt1 == 1 && cnt2 == 1))) { puts("***"); continue; }
dfs(s);
if (cnt != n) { puts("***"); continue; }
for (int i = cnt - 1; i >= 0; i--) {
printf("%s%c", str[path[i]].c_str(), i > 0 ? '.' : '\n');
}
}
}
//有向图 并查集 HDU1116
//判断所有单词能不能连成一串, 如果有多个重复的单词, 也必须满足这样的条件
int n, fa[N], in[N], out[N], p[N];
bool vis[N];
char s[M];
void init() {
memset(vis, 0, sizeof(vis)); memset(in, 0, sizeof(in)); memset(out, 0, sizeof(out));
for (int i = 0; i < N; i++) { fa[i] = i; }
}
int findfa(int n) {
return n == fa[n] ? n : fa[n] = findfa(fa[n]);
}
inline void unite(int x, int y) {
x = findfa(x); y = findfa(y);
if (x != y) { fa[y] = x; }
}
int main() {
int T, n;
scanf("%d", &T);
while (T--) {
init();
scanf("%d", &n);
while (n--) {
scanf("%s", s); int u = s[0] - 'a', v = s[strlen(s) - 1] - 'a';
unite(u, v); out[u]++; in[v]++; vis[u] = vis[v] = true;
}
int cnt = 0, k = 0;
for (int i = 0; i < N; i++) {
fa[i] = findfa(i);
if (vis[i] && fa[i] == i) { cnt++; }
}
if (cnt > 1) { puts("The door cannot be opened."); continue; } //不连通
for (int i = 0; i < N; i++) {
if (in[i] != out[i]) { p[k++] = i; if (k > 2) { break; } }
}
if (k == 0 || (k == 2 && ((out[p[0]] - in[p[0]] == 1 && in[p[1]] - out[p[1]] == 1) ||
(out[p[1]] - in[p[1]] == 1 && in[p[0]] - out[p[0]] == 1)))) {
puts("Ordering is possible.");
} else { puts("The door cannot be opened."); }
}
}
//混合图 POJ1637 (本题保证连通)
//判断欧拉回路 需ISAP + 邻接表 O(V^2*E)
int in[N], out[N];
int main() {
int T, u, v, w;
scanf("%d", &T);
while (T--) {
init();
memset(in, 0, sizeof(in)); memset(out, 0, sizeof(out));
scanf("%d%d", &n, &m);
while (m--) {
scanf("%d%d%d", &u, &v, &w); out[u]++; in[v]++;
if (w == 0) { addedge(u, v, 1); } //双向
}
bool flag = true;
for (int i = 1; i <= n; i++) {
if (out[i] - in[i] > 0) { addedge(0, i, (out[i] - in[i]) >> 1); }
else if (in[i] - out[i] > 0) { addedge(i, n + 1, (in[i] - out[i]) >> 1); }
if (out[i] - in[i] & 1) { flag = false; break; }
}
if (!flag) { puts("impossible"); continue; }
ISAP(0, n + 1, n + 2);
for (int i = head[0]; ~i; i = nxt[i]) {
if (cap[i] > 0 && cap[i] > flow[i]) { flag = false; break; }
}
puts(flag ? "possible" : "impossible");
}
}
//2-SAT
//染色法
const int N = 20005;
const int M = 100005;
int head[N], to[M], nxt[M], tot;
bool vis[N]; //染色标记
int S[N], top; //栈
void init() { tot = 0; memset(head, -1, sizeof(head)); }
void addedge(int x, int y) { to[tot] = y; nxt[tot] = head[x]; head[x] = tot++; }
bool dfs(int u) {
if (vis[u ^ 1]) { return false; }
if (vis[u]) { return true; }
vis[u] = true; S[top++] = u;
for (int i = head[u]; ~i; i = nxt[i]) {
if (!dfs(to[i])) { return false; }
}
return true;
}
bool twoSAT(int n) {
memset(vis, 0, sizeof(vis));
for (int i = 0; i < n; i += 2) {
if (vis[i] || vis[i ^ 1]) { continue; }
top = 0;
if (!dfs(i)) {
while (top) { vis[S[--top]] = false; }
if (!dfs(i ^ 1)) { return false; }
}
}
return true;
}
//HDU 1814
int main() {
int n, m, u, v;
while (~scanf("%d%d", &n, &m)) {
init();
while (m--) {
scanf("%d%d", &u, &v); u--; v--;
addedge(u, v ^ 1); addedge(v, u ^ 1);
}
if (twoSAT(n << 1)) {
for (int i = 0; i < n << 1; i++) {
if (vis[i]) { printf("%d\n", i + 1); }
}
} else { printf("NIE\n"); }
}
}
//Tarjan强连通缩点
const int N = 1005;
const int M = 100005;
int head[N], to[M], nxt[M], tot;
int num[N], Low[N], DFN[N], S[N], Belong[N], idx, top, scc; //Belong数组的值1~scc
bool instack[N];
void init() { tot = 0; memset(head, -1, sizeof(head)); }
void addedge(int x, int y) { to[tot] = y; nxt[tot] = head[x]; head[x] = tot++; }
void Tarjan(int u) {
Low[u] = DFN[u] = ++idx; S[top++] = u; instack[u] = true;
for (int i = head[u]; ~i; i = nxt[i]) {
int v = to[i];
if (!DFN[v]) { Tarjan(v); Low[u] = min(Low[u], Low[v]); }
else if (instack[v] && Low[u] > DFN[v]) { Low[u] = DFN[v]; }
}
if (Low[u] == DFN[u]) {
scc++;
do { v = S[--top]; instack[v] = false; Belong[v] = scc; num[scc]++; } while (v != u);
}
}
bool solvable(int n) { //n是总个数, 需要选择一半
memset(DFN, 0, sizeof(DFN));
memset(instack, 0, sizeof(instack));
memset(num, 0, sizeof(num));
idx = scc = top = 0;
for (int i = 0; i < n; i++) { if (!DFN[i]) { Tarjan(i); } }
for (int i = 0; i < n; i += 2) { if (Belong[i] == Belong[i ^ 1]) { return false; } }
return true;
}
//拓扑排序求任意一组解部分
queue<int> q1, q2;
vector<vector<int>> dag; //缩点后的逆向DAG图
char color[N]; //染色, 为'R'是选择的
int cf[N], indeg[N]; //入度
void solve(int n) {
dag.assign(scc + 1, vector<int>());
memset(indeg, 0, sizeof(indeg));
memset(color, 0, sizeof(color));
for (int u = 0; u < n; u++) {
for (int i = head[u]; ~i; i = nxt[i]) {
int v = to[i];
if (Belong[u] != Belong[v]) { dag[Belong[v]].push_back(Belong[u]); indeg[Belong[u]]++; }
}
}
for (int i = 0; i < n; i += 2) {
cf[Belong[i]] = Belong[i ^ 1]; cf[Belong[i ^ 1]] = Belong[i];
}
while (!q1.empty()) { q1.pop(); }
while (!q2.empty()) { q2.pop(); }
for (int i = 1; i <= scc; i++) { if (indeg[i] == 0) { q1.push(i); } }
while (!q1.empty()) {
int u = q1.front(); q1.pop();
if (color[u] == 0) { color[u] = 'R'; color[cf[u]] = 'B'; }
for (int i = 0; i < (int)dag[u].size(); i++) {
if (--indeg[dag[u][i]] == 0) { q1.push(dag[u][i]); }
}
}
}
int change(char s[]) {
int ret = 0, i = 0;
while (s[i] >= '0' && s[i] <= '9') { ret *= 10; ret += s[i++] - '0'; }
return (ret << 1) + (s[i] != 'w');
}
//POJ3648
int main() {
int n, m, u, v;
char s1[10], s2[10];
while (scanf("%d%d", &n, &m), (n || m)) {
init();
while (m--) {
scanf("%s%s", s1, s2); u = change(s1); v = change(s2);
addedge(u ^ 1, v); addedge(v ^ 1, u);
}
addedge(1, 0);
if (solvable(n << 1)) {
solve(n << 1);
for (int i = 1; i < n; i++) {
//注意这一定是判断color[Belong[
if (color[Belong[i << 1]] == 'R') { printf("%dw", i); }
else { printf("%dh", i); }
putchar(i != n - 1 ? ' ' : '\n');
}
} else { printf("bad luck\n"); }
}
}
//最大团
//搜索 O(n*2^n)
int mp[N][N], stk[N][N], dp[N], ans;
bool dfs(int crt, int tot) {
if (!crt) {
if (tot > ans) { ans = tot; return true; }
return false;
}
for (int i = 0, u, nxt; i < crt; i++) {
u = stk[tot][i]; nxt = 0;
if (crt - i + tot <= ans) { return false; }
if (dp[u] + tot <= ans) { return false; }
for (int j = i + 1; j < crt; j++) {
int v = stk[tot][j];
if (g[u][v]) { stk[tot + 1][nxt++] = v; }
}
if (dfs(nxt, tot + 1)) { return true; }
}
return false;
}
int maxClique(int n) {
ans = 0;
for (int i = n - 1, j, k; i >= 0; i--) {
for (j = i + 1, k = 0; j < n; j++) {
if (g[i][j]) { stk[1][k++] = j; }
}
dfs(k, 1); dp[i] = ans;
}
return ans;
}
//随机贪心 O(T*n^2)
const int T = 1000;
int mp[N][N], id[N], ansn, ans[N]; bool del[N];
void solve(int n) {
memset(del, 0, sizeof(del)); int k = 0;
for (int i = 0, j; i < n; i++) {
if (del[i]) { continue; }
for (j = i + 1, k++; j < n; j++) { if (!mp[id[i]][id[j]]) { del[j] = true; } }
}
if (k > ansn) {
ansn = k;
for (int i = k = 0; i < n; i++) { if (!del[i]) { ans[k++] = id[i]; } }
}
}
void maxClique(int n) {
for (int i = 0; i < n; i++) { id[i] = i; }
for (int t = 0; t < T; t++) {
for (int i = 0; i < n; i++) { swap(id[i], id[rand() % n]); } solve();
}
}
//最大独立集
//随机算法 O(T*(V+E))
const int T = 1000;
int q[N], pos[N]; bool del[N];
int solve(int n) {
int ans = 0;
for (int t = 0; t < T; t++) {
int ret = 0, top = n; memset(del, 0, sizeof(del));
for (int i = 1; i <= n; i++) { q[i] = pos[i] = i; }
while (top) {
int x = rand() % top + 1, u = q[x]; q[x] = q[top--]; pos[q[x]] = x; ret++;
for (int i = head[u]; ~i; i = nxt[i]) {
int v = to[i];
if (!del[v]) { del[v] = true; x = pos[v]; q[x] = q[top--]; pos[q[x]] = x; }
}
}
ans = max(ans, ret);
}
return ans;
}
==========图论-最短路=========
//邻接表
typedef int wtype;
const wtype INF = 0x3f3f3f3f;
int head[N], to[M], nxt[M], tot; wtype len[M];
void init() { tot = 0; memset(head, -1, sizeof(head)); }
void addedge(int x, int y, wtype z) { to[tot] = y; len[tot] = z; nxt[tot] = head[x]; head[x] = tot++; }
//Dijkstra + priority_queue + 邻接矩阵 O(V^2)
typedef int wtype;
struct Node {
int v; wtype w;
bool operator<(const Node &r)const { return w > r.w; }
};
int n, pre[N]; wtype mp[N][N], dist[N]; bool vis[N]; //邻接矩阵初始化为INF
void Dijkstra(int src) {
memset(dist, 0x3f, sizeof(dist));
memset(vis, 0, sizeof(vis)); memset(pre, -1, sizeof(pre));
priority_queue<Node> que; que.push((Node) {src, dist[src] = 0});
while (!que.empty()) {
int u = que.top().v; que.pop();
if (vis[u]) { continue; }
vis[u] = true;
for (int v = 0; v < n; v++) {
if (!vis[v] && dist[v] > dist[u] + mp[u][v]) {
dist[v] = dist[u] + mp[u][v]; pre[v] = u; que.push((Node) {v, dist[v]});
}
}
}
}
//Dijkstra + priority_queue + vector存边 O((V+E)logV)
typedef int wtype;
struct Node {
int v; wtype w;
bool operator<(const Node &r)const { return w > r.w; }
};
vector<Node> e[N]; wtype dist[N]; bool vis[N];
void Dijkstra(int src) {
memset(dist, 0x3f, sizeof(dist));
memset(vis, 0, sizeof(vis));
priority_queue<Node> que; que.push((Node) {src, dist[src] = 0});
while (!que.empty()) {
int u = que.top().v; que.pop();
if (vis[u]) { continue; }
vis[u] = true;
for (int i = 0; i < (int)e[u].size(); i++) {
int v = e[u][i].v; wtype w = e[u][i].w;
if (!vis[v] && dist[v] > dist[u] + w) {
dist[v] = dist[u] + w; que.push((Node) {v, dist[v]});
}
}
}
}
//Dijkstra + priority_queue + 邻接表 O((V+E)logV)
typedef int wtype;
struct Node {
int v; wtype w;
bool operator<(const Node &r)const { return w > r.w; }
};
wtype dist[N];
void Dijkstra(int src) {
memset(dist, 0x3f, sizeof(dist));
priority_queue<Node> que; que.push((Node) {src, dist[src] = 0});
while (!que.empty()) {
int u = que.top().v; wtype w = que.top().w; que.pop();
if (w > dist[u]) { continue; }
for (int i = head[u]; ~i; i = nxt[i]) {
int v = to[i];
if (dist[v] > len[i] + w) {
dist[v] = w + len[i]; que.push((Node) {v, dist[v]});
}
}
}
}
//SPFA + queue/stack + vector存边 O(kE)
typedef int wtype;
struct Edge { int v; wtype w; };
vector<Edge> e[N]; int cnt[N], pre[N]; wtype dist[N]; bool vis[N];
bool SPFA(int src) {
memset(dist, 0x3f, sizeof(dist)); dist[src] = 0;
memset(cnt, 0, sizeof(cnt)); cnt[src] = 1;
memset(vis, 0, sizeof(vis)); vis[src] = true;
memset(pre, -1, sizeof(pre));
queue<int> que; que.push(src); //stack
while (!que.empty()) {
int u = que.front(); que.pop();
vis[u] = false;
for (int i = 0; i < (int)e[u].size(); i++) {
int v = e[u][i].v; wtype w = e[u][i].w;
if (dist[v] > dist[u] + w) {
dist[v] = dist[u] + w; pre[v] = u;
if (!vis[v]) {
vis[v] = true; que.push(v);
if (++cnt[v] > n) { return false; } //有负环回路
}
}
}
}
return true; //没有负环回路
}
//SPFA + SLF + LLL + 邻接表 O(kE)
typedef int wtype;
wtype dist[N];
bool vis[M];
void SPFA(int src) {
memset(dist, 0x3f, sizeof(dist)); dist[src] = 0;
memset(vis, 0, sizeof(vis)); vis[src] = true;
deque<int> que; que.push_back(src);
ll sum = 0;
while (!que.empty()) {
int u = que.front(); que.pop_front();
if (!que.empty() && (ll)dist[u] * que.size() > sum) { que.push_back(u); continue; }
sum -= dist[u]; vis[u] = false;
for (int i = head[u]; ~i; i = nxt[i]) {
int v = to[i]; wtype d = dist[u] + len[i];
if (d < dist[v]) {
if (vis[v]) { sum += d - dist[v]; }
dist[v] = d;
if (!vis[v]) {
if (!que.empty() && dist[que.front()] > dist[v]) { que.push_front(v); }
else { que.push_back(v); }
sum += dist[v]; vis[v] = true;
}
}
}
}
}
//差分约束系统
//a向b连一条权值为c的有向边表示b ? a <= c, 用SPFA判断是否存在负环, 存在即无解
//Bellman-Ford + vector O(VE)
//可以处理负边权图, 可以判断是否存在负环回路, 当且仅当图中不包含从源点可达的负权回路时返回true
typedef int wtype;
struct Edge { int u, v; wtype w; };
vector<Edge> e;
wtype dist[N];
bool BellmanFord(int src) {
memset(dist, 0x3f, sizeof(dist)); dist[src] = 0;
for (int i = 1; i < n; i++) {
bool flag = false;
for (int j = 0; j < (int)e.size(); j++) {
int u = e[j].u, v = e[j].v; wtype w = e[j].w;
if (dist[v] > dist[u] + w) { dist[v] = dist[u] + w; flag = true; }
}
if (!flag) { return true; } //没有负环回路
}
for (int j = 0; j < (int)e.size(); j++) {
int u = e[j].u, v = e[j].v; wtype w = e[j].w;
if (dist[v] > dist[u] + w) { return false; } //有负环回路
}
return true; //没有负环回路
}
//Floyd 带路径记录 O(V^3)
typedef int wtype;
int n, pre[N][N]; wtype mp[N][N]; //初始化为INF
void Floyd() {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) { pre[i][j] = (i == j) ? -1 : i; }
}
for (int k = 1; k <= n; k++) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (mp[i][k] + mp[k][j] < mp[i][j]) {
mp[i][j] = mp[i][k] + mp[k][j]; pre[i][j] = pre[k][j];
}
}
}
}
}
//无负权图的最小环
//有向图: mp[i][i] = INF, 然后跑floyd, ans = min(d[i][i])
//求无向图中经过至少3个点的最小环:
typedef int wtype;
const wtype INF = 0x3f3f3f3f;
int n; wtype mp[N][N], d[N][N]; //初始化为INF
wtype cycFloyd() {
memcpy(d, mp, sizeof(mp)); wtype ret = INF;
for (int k = 1; k <= n; k++) {
for (int i = 1; i < k; i++) {
for (int j = i + 1; j < k; j++) {
ans = min(ans, d[i][j] + mp[i][k] + mp[k][j]);
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
}
}
}
//Astar求k短路
typedef int wtype;
const wtype INF = 0x3f3f3f3f;
int head[N], to[M], nxt[M], tot; wtype len[M];
int rhead[N], rto[M], rnxt[M], rtot; wtype rlen[M];
void init() { tot = rtot = 0; memset(head, -1, sizeof(head)); memset(rhead, -1, sizeof(rhead)); }
void addedge(int x, int y, wtype z) {
to[tot] = y; len[tot] = z; nxt[tot] = head[x]; head[x] = tot++;
rto[rtot] = x; rlen[rtot] = z; rnxt[rtot] = rhead[y]; rhead[y] = rtot++;
}
struct Node {
int v; wtype w;
bool operator<(const Node &r)const { return w > r.w; }
};
int vis[N]; wtype dist[N], ans[N]; //前k短路
void Astar(int src, int des, int k) {
memset(dist, 0x3f, sizeof(dist)); memset(ans, 0x3f, sizeof(ans)); memset(vis, 0, sizeof(vis));
priority_queue<Node> que; que.push((Node) {des, dist[des] = 0});
while (!que.empty()) {
int u = que.top().v; wtype w = que.top().w; que.pop();
if (w > dist[u]) { continue; }
for (int i = rhead[u]; ~i; i = rnxt[i]) {
int v = rto[i];
if (dist[v] > rlen[i] + w) {
dist[v] = w + rlen[i]; que.push((Node) {v, dist[v]});
}
}
}
if (dist[src] < INF) { que.push((Node) {src, dis[src]}); }
while (!que.empty()) {
int u = que.top().v; wtype w = que.top().w; que.pop();
if (u == des && vis[des] <= k) { ans[vis[des]] = w; }
if (vis[u] > k) { continue; }
for (int i = head[u]; ~i; i = nxt[i]) {
int v = to[i]; que.push((Node) {v, w - dist[u] + dist[v] + len[i]});
}
}
}
===========Tarjan 算法求一个无向图的桥
int dfs[MAXN], low[MAXN], tim = 0;
inline void Dfs(int u) {
dfs[u] = low[u] = ++tim;
for (Edge *p = a[u]; p; p = p->next) if (p->flag) {
if (!dfs[p->y]) {
p->opt->flag = false;
Dfs(p->y);
low[u] = min(low[u], low[p->y]);
} else low[u] = min(low[u], dfs[p->y]);
}
for (Edge *p = a[u]; p; p = p->next) if (p->opt->flag == false && low[p->y] > dfs[u]) {
p->bridge = p->opt->bridge = true;
}
}
=========Hopcroft 算法其中二分图左边点的编号为1-n右边点编号为n+1 - Nmatch[i]表示左边的i匹配上的右边的点0无匹配link[i]表示右边的i匹配上的左边的点0无匹配
const int MAXN = 10000;
int match[MAXN];
int levelx[MAXN], levely[MAXN], link[MAXN];
int d[MAXN];
inline bool Bfs(void) {
int head = 1, tail = 0;
memset(levely, 0, sizeof levely);
for (int i = 1; i <= n; i++) {
if (!match[i]) d[++tail] = i;
levelx[i] = 0;
}
bool ret = false;
while (head <= tail) {
int now = d[head++];
for (Edge *p = a[now]; p; p = p->next) if (levely[p->y] == 0) {
levely[p->y] = levelx[now] + 1;
if (link[p->y] == 0) ret = true;
else levelx[link[p->y]] = levely[p->y] + 1, d[++tail] = link[p->y];
}
}
return ret;
}
bool Find(int u) {
for (Edge *p = a[u]; p; p = p->next) if (levely[p->y] == levelx[u] + 1) {
levely[p->y] = 0;
if (link[p->y] == 0 || Find(link[p->y])) {
match[u] = p->y; link[p->y] = u;
return true;
}
}
return false;
}
inline void Match(void) {
while (Bfs())
for (int i = 1; i <= n; i++)
if (!match[i]) Find(i);
}
inline void clear(void) {
memset(match, 0, sizeof match);
memset(link, 0, sizeof link);
memset(levelx, 0, sizeof levelx);
memset(levely, 0, sizeof levely);
}
=============KM算法求最小权完美匹配注意不是全局最大权匹配如果两边点数不相等或者非完美二分图则可以添加虚拟点和权值为inf的边
若求最大权匹配,则将边权取反即可
const int MAXN = 210;
int nx, ny; // 左边的点标号从1到nx右边点标号从1到ny
long long inf, cost[MAXN][MAXN], fx[MAXN], fy[MAXN], dist[MAXN]; //权值若为long long的话只需改动此行即可
int used[MAXN], maty[MAXN], which[MAXN];
inline void AddEdge(int x, int y, int z)
{
cost[x][y] = z;
}
pair<int, long long> KM(void)
{
for (int x = 1; x <= nx; x++)
{
int y0 = 0;
maty[0] = x;
for (int y = 0; y <= ny; y++)
{
dist[y] = inf + 1;
used[y] = false;
}
do
{
used[y0] = true;
int x0 = maty[y0], y1;
long long delta = inf + 1;
for (int y = 1; y <= ny; y++) if (!used[y])
{
long long curdist = cost[x0][y] - fx[x0] - fy[y];
if (curdist < dist[y])
{
dist[y] = curdist;
which[y] = y0;
}
if (dist[y] < delta)
{
delta = dist[y];
y1 = y;
}
}
for (int y = 0; y <= ny; y++) if (used[y])
{
fx[maty[y]] += delta;
fy[y] -= delta;
}
else dist[y] -= delta;
y0 = y1;
}
while (maty[y0] != 0);
do
{
int y1 = which[y0];
maty[y0] = maty[y1];
y0 = y1;
}
while (y0);
}
long long ret = 0;
int npair = 0;
for (int y = 1; y <= ny; y++)
{
int x = maty[y];
if (cost[x][y] < inf)
{
ret += cost[x][y];
npair++;
}
}
return make_pair(npair, ret);
}
inline void clear(void)
{
memset(fx, 0x9f, sizeof fx);
memset(fy, 0x9f, sizeof fy);
memset(cost, 0x3f, sizeof cost);
memset(maty, 0, sizeof maty);
inf = cost[0][0];
}
==================Dinic求解最大流有当前弧优化
struct Edge
{
int y, f;
Edge *next, *opt;
}*a[MAXN], DATA[MAXM << 1], *data = DATA;
inline void Add(int x, int y, int c)
{
Edge *tmp = data++;
tmp->y = y;
tmp->f = c, tmp->next = a[x];
a[x] = tmp;
tmp = data++;
tmp->y = x;
tmp->f = 0;
tmp->next = a[y];
a[y] = tmp;
a[x]->opt = a[y];
a[y]->opt = a[x];
}
int n, m, vs, vt, L;
int level[MAXN], d[MAXN];
inline bool Bfs(void)
{
memset(level, -1, sizeof level);
d[1] = vs;
level[vs] = 0;
int head = 1, tail = 1;
while (head <= tail)
{
int now = d[head++];
e[now] = a[now];
for (Edge *p = a[now]; p; p = p->next) if (p->f > 0&& level[p->y] == -1)
level[d[++tail] = p->y] = level[now] + 1;
}
return level[vt] != -1;
}
inline int Extend(int u, int sum)
{
if (u == vt) return sum;
int r = 0, t;
for (Edge *p = e[u]; p && r < sum; p = p->next) if (level[p->y] == level[u] + 1 && p->f > 0)
{
t = std::min(sum - r, p->f);
t = Extend(p->y, t);
p->f -= t, p->opt->f += t, r += t;
e[u] = p;
}
if (!r) level[u] = -1;
return r;
}
inline int Dinic(void)
{
int r = 0, t;
while (Bfs())
{
while ((t = Extend(vs, inf))) r += t;
}
return r;
}
============spfa求解最小费用最大流
struct Edge
{
int y, f, c;
Edge *next, *opt;
Edge(int y, int f, int c, Edge *next):y(y), f(f), c(c), next(next) {}
}*a[MAXN];
inline void AddEdge(int x, int y, int f, int c)
{
a[x] = new Edge(y, f, c, a[x]);
a[y] = new Edge(x, 0, -c, a[y]);
a[x]->opt = a[y];
a[y]->opt = a[x];
}
int d[MAXN], vis[MAXN], dis[MAXN];
Edge *path[MAXN];
inline pair<int, int> Spfa(void)
{
int Flow = 0, Cost = 0;
while (true)
{
memset(vis, 0, sizeof vis);
memset(dis, 0x7f, sizeof dis);
memset(path, 0, sizeof path);
int head = 1, tail = 1, sum = 1;
d[1] = vs;
vis[vs] = true;
dis[vs] = 0;
while (sum)
{
int now = d[head++];
if (head == MAXN) head = 1;
sum--;
for (Edge *p = a[now]; p; p = p->next) if (p->f > 0 && dis[p->y] > dis[now] + p->c)
{
dis[p->y] = dis[now] + p->c;
path[p->y] = p;
if (!vis[p->y])
{
++tail;
if (tail == MAXN) tail = 1;
sum++;
d[tail] = p->y;
vis[p->y] = true;
}
}
vis[now] = false;
}
if (dis[vt] == dis[0]) return make_pair(Flow, Cost);
int tmp = vt, Min = ~0U>>1;
while (path[tmp])
{
Min = min(Min, path[tmp]->f);
tmp = path[tmp]->opt->y;
}
Flow += Min;
tmp = vt;
while (path[tmp])
{
path[tmp]->f -= Min;
path[tmp]->opt->f += Min;
Cost += Min * path[tmp]->c;
tmp = path[tmp]->opt->y;
}
}
return make_pair(Flow, Cost);
}
================最小树形图算法
使用邻接矩阵存图,返回-1表示无最小树形图。否则tmp[i].second表示i的入边
map[i][j] = 1 表示表示存在一条从i到j的有向边
dis[i][j] 表示从i到j边的长度
typedef pair<int, int> PII;
PII tmp[MAXN];
int dis[MAXN][MAXN];
int map[MAXN][MAXN];
int vis[MAXN], q[MAXN], inp[MAXN];
int Dfs(int u)
{
vis[u] = true;
int ret = 1;
for (int i = 1; i <= n; i++) if (map[u][i] && !vis[i]) ret += Dfs(i);
return ret;
}
int Zhuliu(void)
{
memset(map, 0, sizeof map);
memset(vis, 0, sizeof vis);
if (Dfs(1) != n) return -1;
int done = 0;
while (true)
{
memset(vis, 0, sizeof vis);
memset(inp, 0, sizeof inp);
int Ans = 0;
for (int i = 1; i <= n; i++) vis[i] = i;
for (int i = 2; i <= n; i++)
{
tmp[i] = PII(1000000000, 0);
for (int j = 1; j <= n; j++) if (map[j][i] == 1) tmp[i] = min(tmp[i], PII(dis[j][i], j));
inp[tmp[i].second]++;
if (tmp[i].second) Ans += tmp[i].first;
}
int head = 1, tail = 0;
for (int i = 1; i <= n; i++) if (!inp[i]) q[++tail] = i;
while (head <= tail)
{
int now = q[head++];
if (!--inp[tmp[now].second]) q[++tail] = tmp[now].second;
}
bool ok = true;
for (int i = 1, t; i <= n; i++) if (inp[i] > 0)
{
t = i;
ok = false;
do
{
inp[t] = -i;
t = tmp[t].second;
vis[t] = i;
}
while (t != i);
}
if (ok) return Ans + done;
for (int i = 1; i <= n; i++) if (inp[i] < 0)
{
done += tmp[i].first;
for (int j = 1; j <= n; j++) if (vis[j] != vis[i])
{
if (map[i][j])
{
map[vis[i]][vis[j]] = 1;
dis[vis[i]][vis[j]] = min(dis[vis[i]][vis[j]], dis[i][j]);
}
if (map[j][i])
{
dis[vis[j]][vis[i]] = min(dis[vis[j]][vis[i]], dis[j][i] - tmp[i].first);
map[vis[j]][vis[i]] = 1;
}
}
if (vis[i] != i) for (int j = 1; j <= n; j++) map[i][j] = map[j][i] = 0;
}
}
}