图论模板
Posted streamazure
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了图论模板相关的知识,希望对你有一定的参考价值。
图论模板
最短路
Dijkstra
struct HeapNode {
int u; LL d;
bool operator < (const HeapNode& rhs) const {
return d > rhs.d;
}
};
bool done[maxn];
LL d[maxn];
void dijkstra(int s) {
pq<HeapNode>Q;
for (int i = 0; i <= n; i++) d[i] = INF;
d[s] = 0;
memset(done, 0, sizeof(done));
HeapNode t; t.u = s; t.d = 0;
Q.push(t);
while (!Q.empty()) {
HeapNode x = Q.top(); Q.pop();
int u = x.u;
if (done[u]) continue;
for (int i = head[u]; i; i = e[i].next) {
int v = d[i].to
if (d[u] + e[i].w < d[v]) {
d[v] = d[u] + e[i].w;
t.u = v; t.d = d[v];
Q.push(t);
}
}
done[u] = true;
}
}
SPFA
int cnt[maxn];
bool inq[maxn];
LL 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;
Q.push(s); inq[s] = true;
while (!Q.empty()) {
int u = Q.front(); Q.pop(); inq[u] = false;
for (int i = head[u]; i; i = e[i].next) {
int v = e[i].to;
if (d[u]<INF && d[u] + e[i].w < d[v]) {
d[v] = d[u] + e[i].w;
if (!inq[v]) {
Q.push(v); inq[v] = true;
if (++cnt[v] > n) return false;
//如果某个点迭代了超过n次,说明存在可以无限缩短的最短路,即负环
}
}
}
}
return true;
}
Floyd
LL d[maxn * 4][maxn * 4];
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]);
Johnson
- 创建超级源点0,并以0为起点向图中各结点建边,边权均为0.
- 以0为起点进行SPFA,求出节点0到各节点(i)的最短路,记为(h[i])。由于SPFA的自身特点,这一步可以同时判断负环。
- 更新图上原来所有边的边权(w_i),
w[i]+=h[u]-h[v]
,其中(u,v)是边(i)的起点和终点。 - 枚举图上所有点为起点进行Dijskra,求出图上任意两点(u,v)间的最短路(d[u][v])。
- 更新(d[u][v]),即减去之前加上的边权,
d[u][v]-=h[u]-h[v]
- 最后所得的(d[u][v])即最终答案。
差分约束
不等式标准化
对于不等式(v-u > c),转化为(v-u≥c+1)
对于不等式(v-u=c),转化为(v-u≥c)和(v-u≤c)
求差的最大值,不等式全取(≥),反之取(≤)
建边
(1)求差的最大值,对应最短路。
- 对于不等式(v-u ≤ c),建边
addw(u, v, c)
(2)求差的最小值,对应最长路。
- 对于不等式(v-u≥c),建边
addw(u, v, c)
解的存在性
- 无解:存在负权环
- 任意解:起点终点不连通
最小环判定
void init(){
memset(a,10,sizeof a);
int x,y,z;
for (int i=1;i<=m;i++){
cin >> x >> y >> z;
a[x][y] = a[y][x] = min(a[x][y],z);
}
memcpy(d,a,sizeof a);
}
void work(){
LL ans = INF;
//若有解,则ans为最小环的长度
for (int k = 1;k <= n;k++){
for (int i = 1;i < k;i++)
for (int j = 1;j < i;j++)
ans = min(ans, a[i][k] + a[k][j] + d[i][j]);
for (int i = 1;i <= n;i++)
for (int j = 1;j <= n;j++)
f[i][j] = min(f[i][j], f[i][k] + f[k][j]);
}
}
二分图
二分图判定
bool dfs(int u, int c) {
col[u] = c;
for (int i = head[u]; i; i = e[i].next) {
int v = e[i].to;
if (col[v] == col[u]) return false;
if (col[v] == -1 && !dfs(v, !col[u])) return false;
}
return true;
}
二分图最大匹配/最小点覆盖
bool vis[maxn];
int res[maxn];
//vis[i]记录节点i在试图改变匹配对象时成功与否
//res[i]记录节点i的匹配对象
bool match(int x) {
//注意,参数x都是左部点
for (int i = head[x]; i; i = e[i].next) {
int y = e[i].to;
if (!vis[y]) {
vis[y] = true;
if (!res[y] || match(res[y])) {
res[x] = y;
res[y] = x;
//这里默认左部点和右部点的编号没有重复的
return true;
}
}
}
return false;
}
int main() {
...
int ans = 0;
for (int i = 1; i <= p; i++) {
memset(vis, false, sizeof(vis));
//对于枚举的每一个左部点,右部点的状态都是还没尝试过
if (match(i)) ans++;
}
...
}
二分图最优匹配
bool vis[maxn];
int res[maxn];
int gap;
//记录能使节点配对成功的最小改变量
bool match(int x) {
vis[x] = true;
//别漏了这一步
for (int i = head[x]; i; i = e[i].next) {
int y = e[i].to;
if (!vis[y]) {
int tmp = val[x] + val[y] - e[i].dis;
if (tmp == 0) {
vis[y] = true;
if (!res[y] || match(res[y])) {
res[x] = y;
res[y] = x;
return true;
}
}
else if (tmp > 0) {
gap = min(gap, tmp);
}
}
}
return false;
}
void km() {
for (int i = 1; i <= n; i++) {
while (1) {
gap = INF;
memset(vis, false, sizeof(vis));
if (match(i)) break;
//找不到符合要求的边,降低期望,重新尝试匹配
for (int i = 1; i <= p; i++) {
if (vis[i]) val_x[i] -= gap; //左部点降低期望
}
for (int i = 1; i <= q; i++) {
if (vis[i]) val_y[i] += gap; //右部点提高期望
}
}
}
}
强连通分量
求强连通分量
int dfs_clock, scc_cnt;
int dfn[maxn], low[maxn], sccno[maxn];
void dfs(int u) {
dfn[u] = low[u] = ++dfs_clock;
//给节点u打上时间戳
s.push(u);
for (int i = head[u]; i; i = e[i].next) {
int v = e[i].to;
if (!dfn[v]) {
//节点v还没被搜索到
dfs(v);
low[u] = min(low[u], low[v]);
//维护祖先中最小的时间戳
}
else if (!sccno[v]) {
low[u] = min(low[u], dfn[v]);
//节点v已经被搜索过了,但还不属于某一个SCC
}
}
//对节点u的所有后代节点完成搜索之后
//开始判断节点u是不是这个强连通分量中第一个出现的节点
if (low[u] == dfn[u]) {
scc_cnt++;
//SCC数量+1
while (1) {
int x = s.top(); s.pop();
sccno[x] = scc_cnt;
//给分量中的所有节点记录所在SCC的编号
if (x == u) break;
//访问完u之后,就完成了对这个SCC所有节点的访问,跳出
}
}
}
void find_scc(int n) {
dfs_clock = scc_cnt = 0;
mem(sccno, 0);
mem(dfn, 0);
for (int i = 1; i <= n; i++) {
if (!dfn[i]) dfs(i);
}
}
2-SAT问题
设点(i)为选情况1,点(i+n)为选情况2。根据题意,假定好所有“若A选……则B必须选……”的情况,连边。
如条件“A成立或B成立”,则假定情况“若A不成立,则B必须成立”和“若B不成立,则A必须成立”,连边如下
add(a+n, b)
add(b+n, a)
然后跑Tarjan。如出现sccno[i] == sccno[i+n]
,则无论如何都无法满足题意。
若需要输出字典序最小的一组解,则设点(i)为假,点(i+n)为真,则输出
for (int i = 1; i <= n; i++) {
if (sccno[i] > sccno[i + n]) cout << "1 ";
else cout << "0 ";
}
双连通分量
求双连通分量
stack<int> s;
int dfs(int u, int fa) {
low[u] = dfn[u] = ++dfs_clock;
int child = 0;
for (int i = head[u]; i; i = e[i].next) {
int v = e[i].to;
if (!dfn[v]) {
s.push(e[i]);
child++;
low[v] = dfs(v, u);
low[u] = min(low[u], low[v]);
if (low[v] >= dfn[u]) {
iscut[u] = true;
bcc_cnt++; bcc[bcc_cnt].clear();
for (;;) {
Edge x = s.top(); s.pop();
if (bccno[x.from] != bcc_cnt) {
bcc[bcc_cnt].push_back(x.from);
bccno[x.from] = bcc_cnt;
}
if (bccno[x.to] != bcc_cnt) {
bcc[bcc_cnt].push_back(x.to);
bccno[x.to] = bcc_cnt;
}
if (x.from == u && x.to == v) break;
}
}
}
else if (dfn[v] < dfn[u] && v != fa) {
s.push(e[i]);
low[u] = min(low[u], dfn[v]);
}
}
if (fa < 0 && child == 1) iscut[u] = false;
return low[u];
}
void find_bcc() {
dfs_clock = bcc_cnt = 0;
for (int i = 1; i <= n; i++) {
if (!dfn[i]) dfs(i, -1);
}
}
求割点
int dfs(int u, int fa) {
dfn[u] = low[u] = ++dfs_clock;
int child = 0;
for (int i = head[u]; i; i = e[i].next) {
int v = e[i].to;
if (!dfn[v]) {
child++;
low[v] = dfs(v, u);
low[u] = min(low[v], low[u]);
if (low[v] >= dfn[u]) {
iscut[u]++;
}
}
else if (dfn[v] < dfn[u] && fa != v) {
low[u] = min(low[u], dfn[v]);
}
}
if (fa < 0 && child == 1) iscut[u] = 0;
return low[u];
}
int main(){
...
for (int i = 1; i <= n; i++) {
if (!dfn[i]) dfs(i, -1);
}
...
}
以上是关于图论模板的主要内容,如果未能解决你的问题,请参考以下文章