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