int n, m; cin >> n >> m; vector<vi> mp(n + 1); vi idg2(n + 1); for (int i = 0; i < m; i++) { int u, v; cin >> u >> v; mp[u].push_back(v); idg2[v]++; } queue<int> q; for (int i = 1; i <= n; i++) if (idg2[i] == 0) q.push(i); while (!q.empty()) { if (q.size() > 1) { wuhuan = 0; //这里判环 } auto it = q.front(); q.pop(); ans.push_back(it);//这里输出拓扑序 for (auto v : mp[it]) { idg[v]--; if (!idg[v]) { q.emplace(v); } } }
vector<vi> mp(n + 1, vi(n + 1, LINF)); for (int i = 1; i <= n; i++) mp[i][i] = 0; for (int k = 1; k <= n; k++) { for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { mp[i][j] = min(mp[i][j], mp[i][k] + mp[k][j]); } } }
传递闭包:并查集只能处理无向连通性,传递闭包是有向可达性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
constint N = 2005; bitset<N> f[N]; int n, m;
intmain(){ cin >> n >> m; for (int i = 0; i < n; i++) f[i][i] = 1; for (int i = 0; i < m; i++) { int u, v; cin >> u >> v; f[u][v] = 1; // 有向边 } // Floyd 传递闭包 for (int k = 0; k < n; k++) for (int i = 0; i < n; i++) if (f[i][k]) f[i] |= f[k]; // f[i][j]==1 表示 i 能到达 j }
最短路算法下的子问题->正权最小环
正权图两种做法,按图的形态选:
稠密图 / 邻接矩阵 / n≤500 → Floyd 变体,O(n3)
稀疏图 / 邻接表 / n 大 → Dijkstra × n,O(nmlogn)
稠密图下 Dijkstra × n 退化为 O(n3logn),反而更慢,所以不是"正权图就用 Floyd",而是看图的稠密程度。
for (int i = 0; i <= n; i++) { mp[i][i] = 0; lab[i][i] = 0; }
for (int i = 0; i < m; i++) { int u, v, c; cin >> u >> v >> c; mp[u][v] = min(mp[u][v], c); mp[v][u] = min(mp[v][u], c); lab[u][v] = min(lab[u][v], c); lab[v][u] = min(lab[v][u], c); }
int ans = LINF; vector<vi> pos(n + 1, vi(n + 1, 0)); vi path;
auto fd_huan = [&](int l, int r, auto self) -> void { if (!pos[l][r]) return; int k = pos[l][r]; self(l, k, self); path.push_back(k); self(k, r, self); };
for (int k = 1; k <= n; k++) { // 1. i 和 j 的边界严格小于 k for (int i = 1; i < k; i++) { for (int j = i + 1; j < k; j++) { // 2. 环长度 = 最短路(lab) + 两条原边(mp) if (lab[i][j] < LINF && mp[i][k] < LINF && mp[k][j] < LINF) { if (ans > lab[i][j] + mp[i][k] + mp[k][j]) { path.clear(); path.push_back(i); fd_huan(i, j, fd_huan); path.push_back(j); path.push_back(k); // 3. 补上最大的环节点 k } ans = min(ans, lab[i][j] + mp[i][k] + mp[k][j]); } } }
// 4. Floyd 状态转移全部在 lab 上操作 for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { if (lab[i][k] < LINF && lab[k][j] < LINF) { if (lab[i][j] > lab[i][k] + lab[k][j]) { pos[i][j] = k; lab[i][j] = lab[i][k] + lab[k][j]; } } } } }
if (ans == LINF) { cout << "No solution.\n"; } else { for (auto it : path) { cout << it << ' '; } cout << '\n'; } }
voidsolve() { int n, ml, md; cin >> n >> ml >> md; int a, b, d; vector<vector<pii>> mp(n + 1); for (int i = 1; i < n; i++) { mp[i + 1].push_back({i, 0}); } for (int i = 0; i < ml; i++) { cin >> a >> b >> d; mp[a].push_back({b, d}); } for (int i = 0; i < md; i++) { cin >> a >> b >> d; // swap(a,b); mp[b].push_back({a, -1 * d}); }
for (int i = 1; i <= n; i++) { mp[0].push_back({i, 0}); } vi val(n + 1, INF); val[0] = 0; queue<int> q; q.emplace(0); vi inq(n + 1); inq[0] = 1; vi cnt(n + 1);
while (!q.empty()) { auto now = q.front(); q.pop(); inq[now] = 0; for (auto it : mp[now]) { if (val[it.first] > val[now] + it.second) { val[it.first] = val[now] + it.second; cnt[it.first] = cnt[now] + 1; if (cnt[it.first] > n) { cout << -1 << '\n'; return; } if (!inq[it.first]) {
q.emplace(it.first); inq[it.first] = 1; } } } }
vi val2(n + 1, INF); val2[1] = 0; queue<int> q2; q2.emplace(1); vi inq2(n + 1); inq2[1] = 1; while (!q2.empty()) { auto now = q2.front(); q2.pop(); inq2[now] = 0; for (auto it : mp[now]) { if (val2[it.first] > val2[now] + it.second) { val2[it.first] = val2[now] + it.second; if (!inq2[it.first]) { q2.emplace(it.first); inq2[it.first] = 1; } } } } if (val2[n] == INF) cout << -2 << '\n'; else { cout << val2[n] << '\n'; } }
Tarjan
Tarjan是什么?
O(V+E),其中 V 是节点数,E 是边数
实现:stack dfs
讨论有向图的连通性
Tarjan 算法是找出割点、桥和双连通分量的标准算法。
Tarjan怎么用?
对于每个节点u我们需要
dfn[u] 访问顺序
low[u] 判断u是否属于某个SCC
当$$\text{dfn}[u] = \text{low}[u]$$ 的时候u是这个SCC的根节点
只有当一个节点确定属于某个 SCC 时,它才会被弹出栈。
in_stack[u] (或 vis[u]): 一个布尔数组,标记节点 u 是否在 stack 中。//换名字以免和遍历算法重名
int n, m; cin >> n >> m; vector<vi> mp(n + 1); vi line(n); for (int i = 0; i < m; i++) { int lst = 0, d; for (int j = 0; j < n; j++) { cin >> d; if (lst) { mp[lst].push_back((d)); } if (!i) { line[j] = d; } lst = d; } }
for (int i = 1; i <= n; i++) { if (!dfn[i]) { Tarjan(i, mp); } }
vector<vi> dag(n + 1); for (int i = 1; i <= n; i++) { for (auto v : mp[i]) { if (scc_id[i] != scc_id[v]) { dag[scc_id[i]].push_back(scc_id[v]); } } } vi xiao_scc(n + 1, n + 1); for (int i = 0; i < n; i++) { xiao_scc[scc_id[line[i]]] = min(i, xiao_scc[scc_id[line[i]]]); // cerr<<xiao[scc_id[line[i]]]<<' '<<i<<' '<<line[i]<<'\n'; } for (int i = 1; i <= scc; i++) { for (auto it : dag[i]) { // xiao[i] = min(xiao[i], xiao[it]); xiao_scc[i] = min(xiao_scc[i], xiao_scc[it]); } }
vi ans(n + 1); for (int i = 0; i < n; i++) { // ans[line[i]] = n - xiao[scc_id[line[i]]]-1; ans[line[i]] = n - xiao_scc[scc_id[line[i]]] - 1; } for (int i = 1; i <= n; i++) { cout << ans[i] << ' '; } cout << '\n'; }