1. 程式人生 > 其它 >ABC229 E.F.G 題解

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;
}