Codeforces 147B Smile House(DP預處理 + 倍增)
題目鏈接 Smile House
題意 給定一個$n$個點的有向圖,求一個點數最少的環,使得邊權之和$>0$,這裏的環可以重復經過點和邊。
滿足 $n <= 300$
首先答案肯定是單調的,但是觀察發現只有當我們給所有的點加一個自環的時候才滿足這個性質。
考慮$DP$。設$f[i][j][k]$為長度為$i$,從$j$走到$k$能經過的最大邊權和。
那麽$f[i][j][k] = min(f[i-1][j][l] + g[l][k])$,這樣的預處理是$O(n^{4})$的,$TLE$。
考慮$f[i][j][k]$為長度為$2^{i}$,從$j$走到$k$能經過的最大邊權和。
那麽$f[i][j][k] = min(f[i-1][j][l] + f[i - 1][l][k])$, 這樣的預處理是$O(n^{3}logn)$的。
現在考慮二分答案。把當前驗證的答案$u$拆成不同的幾個$2$的冪次(最多$logn$個)。
令$u = 2^{a_{1}} + 2^{a_{2}} + 2^{a_{3}} + ... + 2 ^ {a_{m}}$
設$c[i][j]$為若只考慮長度為$2^{a_{1}}$, $2^{a_{2}}$, ..., $2^{a_{m}}$的邊,從$i$走到$j$經過的路徑權值和的最大值。
那麽又是一波$O(n^{3}logn)$的轉移。
若最後存在$c[i][i] > 0$,則$u$可行。
時間復雜度$O(n^{3}log^{2}n)$
#include <bits/stdc++.h> using namespace std; #define rep(i, a, b) for (int i(a); i <= (b); ++i) #define dec(i, a, b) for (int i(a); i >= (b); --i) #define MP make_pair #define fi first #define se second typedef long long LL; const int inf = 1e9; const int N = 306; int n, m; int f[10][N][N], c[2][N][N]; int l, r; bool check(int u){ int x = 0; rep(i, 1, n) rep(j, 1, n) c[0][i][j] = -inf * (i != j); rep(st, 0, 9) if (u & (1 << st)){ x ^= 1; rep(i, 1, n) rep(j, 1, n) c[x][i][j] = -inf; rep(k, 1, n) rep(i, 1, n) rep(j, 1, n) c[x][i][j] = max(c[x][i][j], c[x ^ 1][i][k] + f[st][k][j]); } rep(i, 1, n) if (c[x][i][i] > 0) return true; return false; } int main(){ scanf("%d%d", &n, &m); rep(st, 0, 9) rep(i, 0, n + 1) rep(j, 0, n + 1) f[st][i][j] = -inf * (int)(i != j); rep(i, 1, m){ int x, y; scanf("%d%d", &x, &y); scanf("%d%d", &f[0][x][y], &f[0][y][x]); } rep(st, 1, 9){ rep(k, 1, n){ rep(i, 1, n){ rep(j, 1, n){ f[st][i][j] = max(f[st][i][j], f[st - 1][i][k] + f[st - 1][k][j]); } } } } l = 2, r = n; if (!check(r)) return 0 * puts("0"); while (l + 1 < r){ int mid = (l + r) >> 1; if (check(mid)) r = mid; else l = mid + 1; } if (check(l)) printf("%d\n", l); else printf("%d\n", r); return 0; }
但是還有更優的辦法。
我們可以聯想到$O(logn)$求$LCA$時的做法。
$2$的冪次從搞到低依次判斷,若當前的狀態和已經存儲的狀態結合後可以形成正環,那麽恰恰不能取這個冪次。
類比求$LCA$的過程,當$x$往上跳$2^{i}$步後到達的結點和y往上跳$2^{i}$步後到達的結點一樣,那麽恰恰不能往上跳$2^{i}$步。
我們可以類比這個方法求解這道題最後的答案。
也就是不能形成正環的最大值$ans$
別忘了最後$ans$得加$1$
這樣相對前一種方法,復雜度少了一個$log$
時間復雜度$O(n^{3}logn)$
#include <bits/stdc++.h> using namespace std; #define rep(i, a, b) for (int i(a); i <= (b); ++i) #define dec(i, a, b) for (int i(a); i >= (b); --i) const int inf = 1e9; const int N = 306; int n, m; int f[10][N][N], c[N][N], g[N][N]; int l, r; int ans; int main(){ scanf("%d%d", &n, &m); rep(st, 0, 9) rep(i, 0, n + 1) rep(j, 0, n + 1) f[st][i][j] = g[i][j] = -inf * (int)(i != j); rep(i, 1, m){ int x, y; scanf("%d%d", &x, &y); scanf("%d%d", &f[0][x][y], &f[0][y][x]); } rep(st, 1, 9){ rep(k, 1, n){ rep(i, 1, n){ rep(j, 1, n){ f[st][i][j] = max(f[st][i][j], f[st - 1][i][k] + f[st - 1][k][j]); } } } } ans = 0; dec(st, 9, 0){ rep(i, 0, n + 1) rep(j, 0, n + 1) c[i][j] = -inf; rep(k, 1, n) rep(i, 1, n) rep(j, 1, n) c[i][j] = max(c[i][j], g[i][k] + f[st][k][j]); bool flag = false; rep(i, 1, n) if (c[i][i] > 0){ flag = true; break;} if (!flag){ ans += 1 << st; rep(i, 0, n + 1) rep(j, 0, n + 1) g[i][j] = c[i][j]; } } ++ans; printf("%d\n", ans > n ? 0 : ans); return 0; }
Codeforces 147B Smile House(DP預處理 + 倍增)