AcWing 1192. 獎金 [一題三解]
阿新 • • 發佈:2022-04-08
一、拓撲排序+遞推極值
#include <bits/stdc++.h> using namespace std; const int N = 10010, M = 20010; int n, m; int d[N]; int dist[N]; //鄰接表 int e[M], h[N], idx, ne[M]; void add(int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx++; } vector<int> path; //拓撲序路徑 bool topsort() { queue<int> q; for (int i = 1; i <= n; i++) if (!d[i]) q.push(i); int cnt = 0; while (q.size()) { int t = q.front(); q.pop(); path.push_back(t); cnt++; for (int i = h[t]; ~i; i = ne[i]) { int j = e[i]; if (--d[j] == 0) q.push(j); } } return cnt == n; //是否有拓撲序 } int main() { cin >> n >> m; memset(h, -1, sizeof h); for (int i = 0; i < m; i++) { int a, b; cin >> a >> b; add(b, a); d[a]++; } if (!topsort()) //不存在拓撲序 puts("Poor Xed"); else { for (int i = 1; i <= n; i++) dist[i] = 100; //模擬建立超級源點,到每個點的距離是100 for (int i = 0; i < n; i++) { //遍歷拓撲序 int j = path[i]; for (int k = h[j]; ~k; k = ne[k]) //通過點找到所有出邊 //後序必須比前序大,最少是大1個 dist[e[k]] = max(dist[e[k]], dist[j] + 1); //遞推求最長路 } //求最少總獎金 int res = 0; for (int i = 1; i <= n; i++) res += dist[i]; printf("%d\n", res); } return 0; }
二、差分約束(spfa求最長路+判斷正環)
#include <bits/stdc++.h> using namespace std; const int N = 10010, M = 30010; int din[N]; //記錄每個節點入度 int dist[N]; //記錄每個節點距離起點的最小值 int cnt[N]; // cnt[i]記錄以節點i為終點的路徑的邊數 bool st[N]; queue<int> q; int n, m; //鄰接表 int e[M], h[N], idx, w[M], ne[M]; void add(int a, int b, int c) { e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++; } bool spfa() { memset(dist, -0x3f, sizeof dist); q.push(0); st[0] = true; dist[0] = 0; while (q.size()) { int t = q.front(); q.pop(); st[t] = false; for (int i = h[t]; ~i; i = ne[i]) { int j = e[i]; if (dist[j] < dist[t] + w[i]) { dist[j] = dist[t] + w[i]; //判斷是不是存在正環 cnt[j] = cnt[t] + 1; if (cnt[j] >= n + 1) return false; if (!st[j]) { q.push(j); st[j] = true; } } } } return true; } int main() { memset(h, -1, sizeof h); cin >> n >> m; for (int i = 0; i < m; ++i) { int a, b; cin >> a >> b; add(b, a, 1); /** a >= b + 1 */ } //建一個超級源點0號點 for (int i = 1; i <= n; ++i) /** a >= xo + 100 */ add(0, i, 100); if (spfa()) { int res = 0; for (int i = 1; i <= n; ++i) res += dist[i]; cout << res << endl; } else puts("Poor Xed"); return 0; }
三、強連通分量縮點之後遞推求最長路
#include <bits/stdc++.h> using namespace std; const int N = 10010, M = 60010; int dnt[N], low[N]; bool in_sta[N]; //記錄是否在棧中 int dist[N]; //記錄強連通分量距離起點的距離 stack<int> sta; int id[N]; //記錄每個節點的強連通分量的編號 int scc_size[N]; //記錄強連通分量的節點個數 int timestamp; int scc_cnt; //記錄強連通分量的編號 int n, m; int h[N], hs[N], ne[M], e[M], w[M], idx; // hs[u]為強連通分量建的圖的表頭 void add(int h[], int a, int b, int c) { e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++; } void tarjan(int u) { low[u] = dnt[u] = ++timestamp; sta.push(u); in_sta[u] = true; for (int i = h[u]; ~i; i = ne[i]) { int j = e[i]; if (!dnt[j]) { tarjan(j); //有可能存在反向邊遍歷回祖宗節點 low[u] = min(low[u], low[j]); } else if (in_sta[j]) //有可能存在橫向邊和遍歷回之前遍歷過的節點 low[u] = min(low[u], dnt[j]); } if (low[u] == dnt[u]) { int y; ++scc_cnt; do { y = sta.top(); sta.pop(); id[y] = scc_cnt; in_sta[y] = false; scc_size[scc_cnt]++; } while (y != u); } } int main() { memset(h, -1, sizeof h); memset(hs, -1, sizeof hs); cin >> n >> m; for (int i = 0; i < m; ++i) { int a, b; cin >> a >> b; add(h, b, a, 1); /** a >= b + 1 */ } //建一個超級源點0號點 for (int i = 1; i <= n; ++i) /** a >= xo + 100 */ add(h, 0, i, 100); for (int i = 0; i <= n; ++i) if (!dnt[i]) tarjan(i); //縮點(列舉圖中所有每兩個節點,來建圖) bool success = true; for (int i = 0; i <= n; ++i) { for (int j = h[i]; ~j; j = ne[j]) { int k = e[j]; int a = id[i], b = id[k]; if (a == b) { if (w[j]) { //同一個強連通分量內的邊的權值只能為正數,否則存在正環 success = false; break; } } else add(hs, a, b, w[j]); } if (!success) break; } if (!success) puts("Poor Xed"); else { dist[0] = 0; //遞推求出新建的圖中的最長路(按照拓撲序來遞推,scc_cnt ~ 1這個順序符合拓撲序) for (int i = scc_cnt; i >= 1; i--) { //列舉i鄰接的所有的邊,找出最大的狀態轉移 for (int j = hs[i]; ~j; j = ne[j]) { int k = e[j]; dist[k] = max(dist[k], dist[i] + w[j]); } } int res = 0; for (int i = 1; i <= scc_cnt; ++i) res += (scc_size[i] * dist[i]); cout << res << endl; } return 0; }