ABC229 E.F.G 題解
ABC229 E.F.G 題解
E - Graph Destruction
題意:給一張無向圖,每次刪掉一個點及與其聯通的所有邊,
求出第 \(i\) 次刪除後圖中剩餘的連通塊數量。
做法:
一個比較顯然的套路是倒序處理詢問,變刪點為加點,用並查集維護即可。
F - Make Bipartite
題意:給一張有 \(n+1\) 個點的無向有權圖,編號從 \(0\) 到 \(n\),
其中,\(\forall i \in [1,n]\),有邊 \(i \leftrightarrow 0\),權為 \(a_i\),和邊 \(i \leftrightarrow (i\mod n) + 1\),權為 \(b_i\)
要求刪除一部分邊,使得剩下的邊和點構成二分圖,輸出刪除邊的最小邊權和。
做法:
先考慮將 \(n \leftrightarrow 1\) 這條邊斷掉後怎麼做。
考慮每個點與 \(0\) 號點是否在二分圖中的同一個部分,以此作為DP的狀態是可行的,
因為與某個點 \(i\) 直接相連的點只有 \(0,i-1,i+1\),
故順序轉移時只需要在意上一個點和 \(0\) 號點的狀態。
所以,設 \(f_{i,0}\) 代表考慮了前 \(i\) 個點,第 \(i\) 個點與 \(0\) 號點在二分圖中屬於同一邊,
此時保留的邊權和最大值是多少。
至於為什麼是保留邊權和最大,
是因為我們可以將刪除邊權和最小,理解成保留邊權和最多。
那麼同理,我們設 \(f_{i,1}\) 代表考慮了前 \(i\) 個點,第 \(i\) 個點與 \(0\) 號點在二分圖中不屬於同一邊,
此時保留的邊權和最大值是多少。
我們就有轉移式如下:
\(f_{i,0} \gets \max (f_{i - 1, 0}, f_{i - 1, 1} + b_i)\),
\(f_{i,1} \gets \max (f_{i - 1, 0} + a_i + b_i, f_{i - 1, 1} + a_i)\).
這樣轉移的意義是,在保證不出現奇環的情況下,最大化選取的邊權和。
最後再考慮加上 \(n \leftrightarrow 1\) 這條邊的情況。
我們可以列舉初始狀態,即第一個點和 \(0\)
對是和否的情況分別做一次DP,最後求最終答案時,
對不同情況分別判斷 \(n \leftrightarrow 1\) 這條邊是否可取即可。
code:
#define int long long
#define ckmax(a, b) ((a) = max((a), (b)))
#define rep(i, a, b) for (int i = (a); i <= (b); i++)
const int N (2e5 + 10);
int n,a[N],b[N],f[N][2];
inline void init () {
rep (i, 0, n + 5) rep (j, 0, 1) f[i][j] = -1e18;
}
signed main() {
n = read(); int ans = -1e18, sum = 0;
rep (i, 1, n) a[i] = read(), sum += a[i];
rep (i, 1, n) b[i] = read(), sum += b[i];
init(), f[1][0] = 0;
rep (i, 1, n - 1) {
f[i + 1][0] = max (f[i][0], f[i][1] + b[i]);
f[i + 1][1] = max (f[i][0] + a[i + 1] + b[i], f[i][1] + a[i + 1]);
}
ckmax (ans, max (f[n][1] + b[n], f[n][0]));
init(), f[1][1] = a[1];
rep (i, 1, n - 1) {
f[i + 1][0] = max (f[i][0], f[i][1] + b[i]);
f[i + 1][1] = max (f[i][0] + a[i + 1] + b[i], f[i][1] + a[i + 1]);
}
ckmax (ans, max (f[n][0] + b[n], f[n][1]));
cout << sum - ans;
return 0;
}
G - Longest Y
題意:給一個 \(01\) 串和一個數字 \(k\),定義一次操作為交換串中相鄰兩個位置,
求在進行不超過 \(k\) 次操作後,串中連續 \(1\) 的長度最大是多少。
做法:
我們記 \(A_i\) 為第 \(i\) 個 \(1\) 在串中的下標,再記 \(B_i = A_i - i\),問題就可以轉化為:
一次操作為對序列 \(B\) 中某個數加 \(1\) 或減 \(1\),
求在進行不超過 \(k\) 次操作後,\(B\) 中最多有多少個相等的數字。
我們可以二分答案,問題就轉化為了:
讓\(B\) 中有 \(t\) 個相等的數字,需要的最小運算元是多少。
顯然,\(A_i > A_{i-1}\),故 \(B_i = A_i - i \geq A_{i-1} - (i-1) = B_{i-1}\),即 \(B\) 序列不降,
故我們發現,最後變成相等的那若干個數字,在最初的 \(B\) 序列上,一定是連續的,
否則我們將其調整成連續的,一定不會更劣。
也就是說,我們只需要對 \(|B| - t + 1\) 個連續的區間,求出其變成全相等的答案即可。
我們發現,對於一個區間,設其起始點為 \(l\),結束點為 \(r\),那麼其答案可以寫成:
\(\min _ x (\sum _ {i = l} ^ {r} |b_i - x|)\),且該式必然在 \(x = b_{i + \lceil \frac {m}{2} \rceil}\) 時取到。
故我們現在就可以 \(O(1)\) 回答一個區間的答案,
也就可以用 \(O(n \log n)\) 的複雜度解決這個問題了。
code:
#define int long long
#define ckmin(a, b) ((a) = min((a), (b)))
#define rep(i, a, b) for (int i = (a); i <= (b); i++)
const int N (2e5 + 10);
char s[N];
int tot, n, k, a[N], sum[N];
inline int S (int l, int r) {
if (l > r) return 0;
return sum[r] - sum[l - 1];
}
inline bool chk (int x) {
int ans = 1e18;
rep (i, 1, tot) sum[i] = sum[i - 1] + a[i];
rep (l, 1, tot - x + 1) {
int r = l + x - 1, mid = l + (x / 2);
int ls = (mid - l) * a[mid] - S(l, mid - 1);
int rs = S(mid + 1, r) - (r - mid) * a[mid];
ckmin (ans, ls + rs);
}
return ans <= k;
}
signed main() {
cin >> (s + 1) >> k, n = strlen (s + 1);
rep (i, 1, n) if (s[i] == 'Y') a[++tot] = i - tot;
sort (a + 1, a + tot + 1);
int l = 0, r = tot, ans = 0;
while (l <= r) {
int mid = (l + r) >> 1;
if (chk(mid)) ans = mid, l = mid + 1;
else r = mid - 1;
}
return cout << ans, 0;
}