ARC194A Operations on a Stack
发现一定是删掉若干段偶数长度的不选,直接 DP 即可做到线性。
void slv() {int n = Read<int>();vector<int> A(n);for (int i = 0; i < n; i ++) {Read(A[i]);}vector<array<ll, 3>> f(n);f[0][2] = A[0], f[0][1] = 0, f[0][0] = -inf;for (int i = 1; i < n; i ++) {f[i][2] = A[i] + max(f[i - 1][0], f[i - 1][2]);f[i][1] = max(f[i - 1][0], f[i - 1][2]);f[i][0] = f[i - 1][1];}Write(max(f[n - 1][0], f[n - 1][2]), '\n');return;
}
ARC194B Minimum Cost Sort
考虑下界。
对于每个数 \(i\),如果存在逆序对 \((j, i)\),那么就至少需要和 \(j\) 交换一次.,那么下界就是一个等差数列求和。
记 \(c_i\) 表示 \(i\) 位置的逆序对个数,不难得到下界为:
用一种你喜欢的方式求出逆序对后计算即可。
void slv() {int n = Read<int>();ll ans = 0; Fenwick<int> tr(n + 1);for (int i = 1; i <= n; i ++) {int x = Read<int>();int s = tr.Query(n - x + 1);ans += 1LL * ((i - s) + (i - 1)) * ((i - 1) - (i - s) + 1) / 2;tr.Update(n - x + 1, 1);}Write(ans, '\n');return;
}
ARC194C Cost to Flip
一定是先把一些 \(1\) 变成 \(0\),然后再把 \(0\) 变成 \(1\)。
其中第一步操作的一定是所有的 \(A_i = 1, B_i = 0\) 位置和一部分 \(A_i = B_i = 1\) 的位置,第二步操作的一定是所有的 \(A_i = 0, B_i = 1\) 的位置和在第一步中操作过的 \(A_i = B_i = 1\) 的位置。
通过进一步的观察,发现对于 \(A_i = B_i = 1\) 的位置,我们只会改按 \(C_i\) 从大到小排序后的一段前缀。
然后就枚举这段前缀随便统计一下答案啥的,反正细节很多,烦。
void slv() {int n = Read<int>();vector<int> A(n), B(n), C(n);for (int i = 0; i < n; i ++) {Read(A[i]);}for (int i = 0; i < n; i ++) {Read(B[i]);}for (int i = 0; i < n; i ++) {Read(C[i]);}vector<int> pA, pB, pC;for (int i = 0; i < n; i ++) {if (A[i] == 1 && B[i] == 0) {pA.emplace_back(i);} else if (A[i] == 0 && B[i] == 1) {pB.emplace_back(i);} else if (A[i] == 1 && B[i] == 1) {pC.emplace_back(i);}}sort(pA.begin(), pA.end(), [&](int i, int j) { return C[i] > C[j]; });sort(pB.begin(), pB.end(), [&](int i, int j) { return C[i] < C[j]; });sort(pC.begin(), pC.end(), [&](int i, int j) { return C[i] > C[j]; });const int nA = pA.size(), nB = pB.size(), nC = pC.size();vector<ll> f(1 + nC);{ll s = 0;for (int i = 0; i < n; i ++)if (A[i] == 1) s += C[i];for (int i = 0; i < nA; i ++) {s -= C[pA[i]], f[0] += s;}s = 0;for (int i = 0; i < n; i ++)if (A[i] == 1) s += C[i];for (int i = 1, j = 0; i <= nC; i ++) {while (j < nA && C[pA[j]] > C[pC[i - 1]]) s -= C[pA[j ++]];s -= C[pC[i - 1]];f[i] = f[i - 1] - 1LL * C[pC[i - 1]] * (nA - j) + s;}};vector<ll> g(1 + nC);{ll s = 0;for (int i = 0; i < n; i ++)if (B[i] == 1 && A[i] == 1) s += C[i];for (int i = 0; i < nB; i ++) {s += C[pB[i]], g[0] += s;}s = 0;for (int i = 0; i < n; i ++)if (B[i] == 1) s += C[i];for (int i = 1, j = nB - 1; i <= nC; i ++) {while (j >= 0 && C[pB[j]] > C[pC[i - 1]]) s -= C[pB[j --]];g[i] = g[i - 1] + s - 1LL * C[pC[i - 1]] * (j + 1);s -= C[pC[i - 1]];}};ll ans = 1E18;for (int i = 0; i <= nC; i ++)cmin(ans, f[i] + g[i]);Write(ans, '\n');return;
}
ARC194D Reverse Brackets
建括号树,操作就是选一段连续的儿子区间,然后将 这段区间 和 其所有后代的儿子 reverse 一下。
同时还要对后代操作非常烦,但是可以通过对所有儿子操作一次变成 reverse 一段连续的儿子区间。
那么现在要做的就是每次可以 reverse 一段连续的儿子区间,问能生成多少棵本质不同的树。
考虑 DFS 计算答案,对于一个点,如果直接算儿子的方案数的乘积然后任意排列的话会算重。
进一步分析,发现 \(size\) 不同的子树是不互相影响的,\(size\) 相同的子树可以钦定必须是从左到右的顺序,这样就不会算重了。
暴力模拟上面的过程是 \(O(n^2)\) 的。
constexpr int N = 5e3 + 5;
char s[N];void slv() {int n; Read(n), Read(s + 1);vector<int> sum(n + 1);for (int i = 1; i <= n; i ++) {sum[i] = (s[i] == '(' ? 1 : -1);}partial_sum(sum.begin(), sum.end(), sum.begin());int cur = 0;map<vector<int>, int> id;id[vector<int>()] = 0;auto ID = [&](const vector<int> &v) {if (!id.count(v)) {id[v] = ++ cur;}return id[v];};auto Solve = [&](auto self, int L, int R) -> pair<int, mint> {if (L > R) {return {0, 1};}vector<pair<int, mint>> son;for (int l = L, r; l <= R; l = r + 1) {r = l;while (sum[++ r] != sum[l - 1]);son.emplace_back(self(self, l + 1, r - 1));}sort(son.begin(), son.end());const int n = son.size();mint ans = 1;for (int l = 0, r; l < n; l = r + 1) {r = l, ans *= son[l].sec;while (r + 1 < n && son[r + 1].fir == son[l].fir) {ans *= son[++ r].sec;}ans *= comb.C(r + 1, r - l + 1);}vector<int> hsh(n);for (int i = 0; i < n; i ++)hsh[i] = son[i].fir;return {ID(hsh), ans};};Write((int)Solve(Solve, 1, n).sec, '\n');return;
}
ARC194E Swap 0^X and 1^Y
对于一段连续的 \(1\),将它分成若干段 \(= X\) 的 \(A\) 和 \(< X\) 个 \(1\),\(0\) 同理分成 \(B\) 和 \(0\)。
不难发现,如果我们允许 \(A\) 和 \(1\),\(B\) 和 \(0\),\(A\) 和 \(B\) 之间的交换,那么这个和原问题是没有区别的,因为不会发生初始不同的 \(0 / 1\) 的连续段之间的拼接。
发现此时 \(0\) 限制住了 \(1\) 和 \(A\) 的移动,\(1\) 限制住了 \(0\) 和 \(B\) 的移动,所以直接把相邻 \(0\) 之间 \(1\) 的个数、相邻 \(0\) 之间 \(A\) 的个数,相邻 \(1\) 之间 \(B\) 的个数这三个东西看成特征值,特征值相等的序列一定能互相转化。
求出 \(S\) 和 \(T\) 的特征值之后进行判定即可,时间复杂度线性。
constexpr int N = 5E5 + 5;
char s[N], t[N];void slv() {int N, X[2];Read(N, X[0], X[1]);vector<int> A(N), B(N);Read(s), Read(t);for (int i = 0; i < N; i ++) {A[i] = s[i] - '0';B[i] = t[i] - '0';}auto calc = [&](const vector<int> &a, int x, int y) {const int n = a.size();vector<int> ans; int cnt = 0;for (int i = 0; i < n; i ++) {if (a[i] == x) {++ cnt;} else if (a[i] == y) {ans.emplace_back(cnt);cnt = 0;}}ans.emplace_back(cnt);return ans;};auto trans = [&](const vector<int> &a) -> tuple<vi, vi, vi> {const int n = a.size();vector<int> b;for (int l = 0, r; l < n; l = r + 1) {r = l; int o = a[l];while (r + 1 < n && a[r + 1] == a[l]) {++ r;}int len = r - l + 1;while (len >= X[o]) {b.emplace_back(2 + o);len -= X[o];}while (len) {b.emplace_back(o);-- len;}}return {calc(b, 0, 1), calc(b, 0, 3), calc(b, 1, 2)};};Yes(trans(A) == trans(B));return;
}