graph.cpp
来自「ACM经典算法ACM经典算法ACM经典算法」· C++ 代码 · 共 590 行 · 第 1/2 页
CPP
590 行
return 1;
}
/*============================================================*\
| Johnson所有点对最短路 |
\*============================================================*/
Johnson(G)
compute G' where V[G'] = V[G] + {s} and
E[G'] = E[G] + {(s, v) : v in V[G]}
IF Bellman-ford(G', w, s) = false THEN
G has a negative-weight cycle.
ELSE FOR each vertex v in V[G'] DO
set h[v] = d(s, v)
FOR each edge (u, v) in E[G'] DO
w'(u, v) = w(u, v) + h[u] - h(v)
FOR each vertex u in V[G] DO
run Dijkstra(G, w', u) to compute
d'(u, v) for all v in V[G]
FOR each vertex v in V[G] DO
D[u][v] = d'[u, v] + h[v] - h[u]
return D[][]
/*============================================================*\
| Prim求MST |
| INIT: cost[][]耗费矩阵(inf为无穷大); |
| CALL: prim(cost, n); 返回-1代表原图不连通; |
\*============================================================*/
#define typec int // type of cost
const typec inf = 0x3f3f3f3f; // max of cost
int vis[V]; typec lowc[V];
typec prim(typec cost[][V], int n) // vertex: 0 ~ n-1
{
int i, j, p;
typec minc, res = 0;
memset(vis, 0, sizeof(vis));
vis[0] = 1;
for (i=1; i<n; i++) lowc[i] = cost[0][i];
for (i=1; i<n; i++) {
minc = inf; p = -1;
for (j=0; j<n; j++) if (0 == vis[j] && minc > lowc[j]) {
minc = lowc[j]; p = j;
}
if (inf == minc) return -1; // 原图不连通
res += minc; vis[p] = 1;
for (j=0; j<n; j++)
if (0 == vis[j] && lowc[j] > cost[p][j])
lowc[j] = cost[p][j];
}
return res;
}
/*============================================================*\
| 有向图最小树形图 |
| INIT: eg置为边表; res置为0; cp[i]置为i; |
| CALL: dirtree(root, nv, ne); res是结果; |
\*============================================================*/
#define typec int // type of res
const typec inf = 0x3f3f3f3f; // max of res
typec res, dis[V];
int to[V], cp[V], tag[V];
struct Edge { int u, v; typec c; } eg[E];
int iroot(int i)
{
if (cp[i] == i) return i;
return cp[i] = iroot(cp[i]);
}
int dirtree(int root, int nv, int ne) // root: 树根
{
int i, j, k, circle = 0;
memset(tag, -1, sizeof(tag));
memset(to, -1, sizeof(to));
for (i = 0; i < nv; ++i) dis[i] = inf; // vertex: 0 ~ n-1
for (j = 0; j < ne; ++j) {
i = iroot(eg[j].u); k = iroot(eg[j].v);
if (k != i && dis[k] > eg[j].c) {
dis[k] = eg[j].c;
to[k] = i;
}
}
to[root] = -1; dis[root] = 0; tag[root] = root;
for (i = 0; i < nv; ++i) if (cp[i] == i && -1 == tag[i]) {
j = i;
for ( ; j != -1 && tag[j] == -1; j = to[j]) tag[j] = i;
if (j == -1) return 0;
if (tag[j] == i) {
circle = 1; tag[j] = -2;
for (k = to[j]; k != j; k = to[k]) tag[k] = -2;
}
}
if (circle) {
for (j = 0; j < ne; ++j) {
i = iroot(eg[j].u); k = iroot(eg[j].v);
if (k != i && tag[k] == -2) eg[j].c -= dis[k];
}
for (i = 0; i < nv; ++i) if (tag[i] == -2) {
res += dis[i]; tag[i] = 0;
for (j = to[i]; j != i; j = to[j]) {
res += dis[j]; cp[j] = i; tag[j] = 0;
}
}
if (0 == dirtree(root, nv, ne)) return 0;
} else {
for (i = 0; i < nv; ++i) if (cp[i] == i) res += dis[i];
}
return 1; // 若返回0代表原图不连通
}
/*============================================================*\
| Minimal Steiner Tree |
| G(V, E), A是V的一个子集, 求至少包含A中所有点的最小子树. |
| 时间复杂度: O(N^3 + N * 2^A * (2^A + N)) |
| INIT: d[][]距离矩阵; id[]置为集合A中点的标号; |
| CALL: steiner(int n, int a); |
| main()函数解决的题目: Ticket to Ride, NWERC 2006/2007 |
| 给4个点对(a1, b1) ... (a4, b4), 求min(sigma(dist[ai][bi])) |
| 其中重复的路段只能算一次. |
| 这题要找出一个steiner森林, 最后要对森林中树的个数进行枚举 |
\*============================================================*/
#define typec int // type of cost
const typec inf = 0x3f3f3f3f; // max of cost
int vis[V], id[A]; // id[]: A中点的标号
typec d[V][V], dp[1<<A][V]; // dp[i][v]: 点v到点集i的最短距离
void steiner(int n, int a)
{
int i, j, k, mx, mk, top = (1 << a);
for (k = 0; k < n; k++) for (i = 0; i < n; i++)
for (j = 0; j < n; j++) if (d[i][j] > d[i][k] + d[k][j])
d[i][j] = d[i][k] + d[k][j];
for (i = 0; i < a; i++) { // vertex: 0 ~ n-1
for (j = 0; j < n; j++)
dp[1 << i][j] = d[j][ id[i] ];
}
for (i = 1; i < top; i++) {
if ( 0 == (i & (i - 1)) ) continue;
memset(vis, 0, sizeof(vis));
for (k = 0; k < n; k++) { // init
for (dp[i][k] = inf, j = 1; j < i; j++)
if ((i | j) == i)
if (dp[i][k] > dp[j][k] + dp[i - j][k])
dp[i][k] = dp[j][k] + dp[i - j][k];
}
for (j = 0; mx = inf, j < n; j++) { // update
for (k = 0; k < n; k++)
if (dp[i][k] <= mx && 0 == vis[k])
mx = dp[i][mk = k];
for (k = 0, vis[mk] = 1; k < n; k++)
if (dp[i][mk] > dp[i][k] + d[k][mk])
dp[i][mk] = dp[i][k] + d[k][mk];
}
}
}
int main(void)
{
int n, a = 8;
// TODO: read data;
steiner(n, a);
// enum to find the result
for (i = 0, b = inf; z = 0, i < 256; b > z ? b = z : b, i++)
for (j = 0; y = 0, j < 4; z += !!y * dp[y][x], j++)
for (k = 0; k < 8; k += 2) if ((i >> k & 3) == j)
y += 3 << k, x = id[k];
// TODO: cout << b << endl;
return 0;
}
/*============================================================*\
| Tarjan强连通分量 |
| INIT: vec[]为邻接表; stop, cnt, scnt置0; pre[]置-1; |
| CALL: for (i=0; i<n; ++i) if (-1 == pre[i]) tarjan(i, n); |
\*============================================================*/
vector<int> vec[V];
int id[V], pre[V], low[V], s[V], stop, cnt, scnt;
void tarjan(int v, int n) // vertex: 0 ~ n-1
{
int t, minc = low[v] = pre[v] = cnt++;
vector<int>::iterator pv;
s[stop++] = v;
for (pv = vec[v].begin(); pv != vec[v].end(); ++pv) {
if(-1 == pre[*pv]) tarjan(*pv, n);
if(low[*pv] < minc) minc=low[*pv];
}
if(minc < low[v]) {
low[v] = minc; return;
}
do {
id[t = s[--stop]] = scnt; low[t] = n;
} while(t != v);
++scnt; // 强连通分量的个数
}
/*============================================================*\
| 2-sat问题 |
| n个集团, 每个集团2个人, 现在要选出n个人当代表, 且每个集团|
| 只能选出一个人. 如果两人有矛盾, 他们不能同时被选中, 求一组 |
| 可行解. |
\*============================================================*/
构造有向图G. G包含2n个顶点, 代表2n个人. 于是问题转化为从G中
选出n个顶点, 使其满足2-sat条件. 当存在矛盾边(i, j)时, 表示当选
择i时, 必须选择j', 同样当选择j时, 必须选择i'. 于是向G中添加两
条弧: (i, j')和(j, i').
1. 求G的所有强连通分量. 很明显, 如果我们选中强连通分量中的
任一点, 那么该强连通分量中的其它点也必须被选中. 如果i和i'属于
同一强连通分量, 则矛盾产生, 该2-sat无解.
2. 若无矛盾, 把处于同一强连通分量中的点和边缩成一个点, 得到
图G'. 现在G'肯定不存在圈.
3. 把G'中的所有点置为未着色, 按照拓扑顺序重复下面操作:
i. 选择第一个未着色顶点x, 把x染成红色;
ii. 把所有与x矛盾的点y及y的所有子孙全部染成蓝色;(若存
在<i, i'>, 其中i属于x, i'属于y, 则x与y矛盾)
iii.重复i和ii, 直至结束.
iv. G'中被染成红色的点对应的G中的点集, 就是该2-sat的一个解.
/*============================================================*\
| 弦图判断 |
| INIT: g[][]置为邻接矩阵; |
| CALL: mcs(n); peo(n); |
| 第一步: 给节点编号 mcs(n) |
| 设已编号的节点集合为A, 未编号的节点集合为B |
| 开始时A为空, B包含所有节点. |
| for num=n-1 downto 0 do { |
| 在B中找节点x, 使与x相邻的在A集合中的节点数最多, |
| 将x编号为num, 并从B移入A. |
| } |
| 第二步: 检查 peo(n) |
| for num=0 to n-1 do { |
| 对编号为num的点x, 设所有编号>num且与x相邻的点集为C |
| 在C中找出编号最小的节点y, |
| 若C中存在点z!=y, 使得y与z之间无边, 则此图不是弦图. |
| } |
| 检查完了, 则此图是弦图. |
\*============================================================*/
int g[V][V], order[V], inv[V], tag[V];
void mcs(int n)
{
int i, j, k;
memset(tag, 0, sizeof(tag));
memset(order, -1, sizeof(order));
for (i = n - 1; i >= 0; i--) { // vertex: 0 ~ n-1
for (j = 0; order[j] >= 0; j++) ;
for (k = j + 1; k < n; k++)
if (order[k] < 0 && tag[k] > tag[j]) j = k;
order[j] = i, inv[i] = j;
for (k = 0; k < n; k++) if (g[j][k]) tag[k]++;
}
}
int peo(int n)
{
int i, j, k, w, min;
for (i = n - 2; i >= 0; i--) {
j = inv[i], w = -1, min = n;
for (k = 0; k < n; k++)
if (g[j][k] && order[k] > order[j] && order[k] < min)
min = order[k], w=k;
if (w < 0) continue;
for (k = 0; k < n; k++)
if (g[j][k] && order[k] > order[w] && !g[k][w])
return 0; // no
}
return 1; // yes
}
/*============================================================*\
| 弦图的perfect elimination点排列 |
| INIT: g[][]置为邻接矩阵; |
| CALL: cardinality(n); tag[i]为排列中第i个点的标号; |
| The graph with the property mentioned above is called |
| chordal graph. A permutation s = [v1 , v2 ,..., vn] of the |
| vertices of such graph is called a perfect elimination |
| order if each vi is a simplicial vertex of the subgraph |
| of G induced by { vi ,..., vn}. A vertex is called |
| simplicial if its adjacency set induces a complete |
| subgraph, that is, a clique (not necessarily maximal). |
| The perfect elimination order of a chordal graph can be |
| computed with the following codes: |
\*============================================================*/
procedure maximum cardinality search(G, s)
for all vertices v of G do
set label[v] to zero
end for
for all i from n downto 1 do
choose an unnumbered vertex v with largest label
set s(v) to i{number vertex v}
for all unnumbered vertices w adjacent to vertex v do
increment label[w] by one
end for
end for
end procedure
int tag[V], g[V][V], deg[V], vis[V];
void cardinality(int n)
{
int i, j, k;
memset(deg, 0, sizeof(deg));
memset(vis, 0, sizeof(vis));
for (i = n - 1; i >= 0; i--) {
for (j = 0, k = -1; j < n; j++) if (0 == vis[j]) {
if (k == -1 || deg[j] > deg[k]) k = j;
}
vis[k] = 1, tag[i] = k;
for (j = 0; j<n; j++)
if (0 == vis[j] && g[k][j]) deg[j]++;
}
}
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?