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;
}
posted @ 2021-12-16 17:35  GaryH  阅读(139)  评论(0编辑  收藏  举报