AtCoder Regular Contest 193 (Div. 1)
ARC193A Complement Interval Graph
不难发现最多跳两步,分类讨论一下即可。
constexpr ll inf = 1E18;
void slv() {
int n; Read(n);
vector<int> W(n);
for (int i = 0; i < n; i ++) {
Read(W[i]);
}
vector<pair<int, int>> seg(n);
for (int i = 0; i < n; i ++) {
int l, r; Read(l, r);
seg[i] = {l, r};
}
vector<int> idl(n), idr(n);
iota(idl.begin(), idl.end(), 0);
iota(idr.begin(), idr.end(), 0);
sort(idl.begin(), idl.end(), [&](int i, int j) { return seg[i].fir < seg[j].fir; });
sort(idr.begin(), idr.end(), [&](int i, int j) { return seg[i].sec < seg[j].sec; });
vector<int> pre(n), suf(n);
pre.front() = W[idr.front()];
suf.back() = W[idl.back()];
for (int i = 1; i < n; i ++) {
pre[i] = min(pre[i - 1], W[idr[i]]);
}
for (int i = n - 2; ~i; i --) {
suf[i] = min(suf[i + 1], W[idl[i]]);
}
auto Pre = [&](int i) -> ll {
int l = -1, r = n - 1;
while (l < r) {
int mid = (l + r + 1) / 2;
if (seg[idr[mid]].sec < seg[i].fir) {
l = mid;
} else {
r = mid - 1;
}
}
return l == -1 ? inf : pre[l];
};
auto Suf = [&](int i) -> ll {
int l = 0, r = n;
while (l < r) {
int mid = (l + r) / 2;
if (seg[idl[mid]].fir > seg[i].sec) {
r = mid;
} else {
l = mid + 1;
}
}
return l == n ? inf : suf[l];
};
int q = Read<int>();
while (q --) {
int s, t;
Read(s, t), -- s, -- t;
if (seg[s].sec < seg[t].fir || seg[t].sec < seg[s].fir) {
Write(W[s] + W[t], '\n'); continue;
}
ll ans = inf;
int mn = seg[s].fir < seg[t].fir ? s : t;
int mx = seg[s].sec > seg[t].sec ? s : t;
cmin(ans, Pre(mn));
cmin(ans, Suf(mx));
cmin(ans, Pre(s) + Suf(t));
cmin(ans, Pre(t) + Suf(s));
if (ans == inf) { Puts("-1"); continue; }
Write(ans + W[s] + W[t], '\n');
}
return;
}
ARC193B Broken Wheel
只需对 \((d_0, d_1, \cdots, d_{n - 1})\) 计数,因为度数和固定。
考虑给你一个 \(d\) 序列怎么判定是否合法,做法是 \(f_{i, 0 / 1, 0 / 1}\) 表示考虑到了第 \(i\) 个位置,当 \((i, i + 1)\) 这条边定向为 \(i + 1 \rightarrow i\) 时是否可行,定向为 \(i \rightarrow i + 1\) 时是否可行,转移是平凡的。
计数可以直接 DP of DP。
时间复杂度线性,有 \(64\) 的常数。
constexpr int N = 1e6 + 5;
char s[N];
void slv() {
int n = Read<int>();
Read(s);
vector<int> A(n);
for (int i = 0; i < n; i ++) {
A[i] = s[i] - '0';
}
vector<array<array<mint, 4>, 4>> f(n);
array<array<array<int, 2>, 4>, 4> trans;
[&]() {
for (int S = 0; S < 4; S ++) {
for (int d = 0; d < 4; d ++) {
for (int o = 0; o < 2; o ++) {
int l = S & 1, r = S >> 1;
int L = 0, R = 0;
L |= l && (1 <= d && d <= 1 + o);
L |= r && (2 <= d && d <= 2 + o);
R |= l && (0 <= d && d <= 0 + o);
R |= r && (1 <= d && d <= 1 + o);
trans[S][d][o] = L | (R << 1);
}
}
}
return;
}();
f[0][0 | (1 << 1)][0 | (0 << 1)] ++;
f[0][1 | (A[0] << 1)][0 | (1 << 1)] ++;
f[0][A[0] | (0 << 1)][1 | (A[0] << 1)] ++;
f[0][0 | (0 << 1)][A[0] | (0 << 1)] ++;
for (int i = 0; i + 1 < n; i ++) {
for (int S0 = 0; S0 < 4; S0 ++) {
for (int S1 = 0; S1 < 4; S1 ++) {
if (!f[i][S0][S1]) {
continue;
}
for (int d = 0; d < 4; d ++) {
const int T0 = trans[S0][d][A[i + 1]];
const int T1 = trans[S1][d][A[i + 1]];
f[i + 1][T0][T1] += f[i][S0][S1];
}
}
}
}
mint ans = 0;
for (int S0 = 0; S0 < 4; S0 ++) {
for (int S1 = 0; S1 < 4; S1 ++) {
if ((S0 & 1) == 1 || (S1 >> 1) == 1) {
ans += f[n - 1][S0][S1];
}
}
}
Write((int)ans, '\n');
return;
}
ARC193C Grid Coloring 3
时光倒流,判定是第一次删一个同色的十字,之后每次可以删同色的行、列、十字,能删空就合法。
记 \(f_{i, j}\) 表示 \(i\) 行 \(j\) 列的网格中,能通过删同色的行、列、十字删空的个数。
转移时找到同色的位置集合,随便删的话一个会算重,考虑每次钦定一个子集并删掉,容斥计算答案。
对于一个网格,有两种情况:同色位置的集合中只有行或只有列、同色位置的集合中同时有行和列。
对于同色的恰好有 \(k\) 行的情况,钦定 \(j\) 行同色,那么要求 \(\displaystyle \sum_{1 \le j \le k} \binom{k}{j} c_j = 1\),不难得到容斥系数 \(c_j = (-1)^{j + 1}\)。列的情况是相同的。
对于同色的恰好有 \(i\) 行 \(j\) 列的情况,在只钦定行和只钦定列的时候已经被算了两次,所以我们需要在同时钦定行列的时候让它的贡献为 \(-1\)。
钦定 \(x\) 行 \(y\) 列同色,那么就是要求 \(\displaystyle \sum_{1 \le x \le i} \sum_{1 \le y \le j} \binom{i}{x} \binom{j}{y} c_{x, y} = -1\),可以得到 \(c_{x, y} = (-1)^{x + y + 1}\)。
这样我们就得到了转移:
这样就能在 \(O(n^4)\) 的时间复杂度内得到 \(f\)。
统计答案就是上面的第三种情况要求贡献为 \(1\),所以令容斥系数 \(c_{x, y} = (-1)^{x + y}\) 即可得到:
这样就得到了 \(O(n^4)\) 的做法。
瓶颈在于同时钦定行列的情况的转移,也就是计算:
记 \(\displaystyle g_{i, j} = \sum_{1 \le y \le j} \binom{j}{y} (-1)^{y + 1} f_{i, j - y}\),那么转移就变成了:
这样就做到了 \(O(n^3)\)。
void slv() {
int n, m, C;
Read(n, m, C);
vector<mint> pw(n + m + 1);
pw[0] = 1;
for (int i = 1; i <= n + m; i ++) {
pw[i] = pw[i - 1] * C;
}
vector f(n + 1, vector<mint>(m + 1));
vector g(n + 1, vector<mint>(m + 1));
for (int i = 0; i <= n; i ++) {
for (int j = 0; j <= m; j ++) {
if (!i || !j) {
f[i][j] = g[i][j] = 1;
continue;
}
for (int r = 1; r <= i; r ++) {
mint coef = (r & 1) ? 1 : -1;
coef *= comb.C(i, r);
f[i][j] += coef * f[i - r][j] * pw[r];
}
for (int c = 1; c <= j; c ++) {
mint coef = (c & 1) ? 1 : -1;
coef *= comb.C(j, c);
f[i][j] += coef * f[i][j - c] * pw[c];
g[i][j] += coef * f[i][j - c];
}
for (int r = 1; r <= i; r ++) {
mint coef = (r & 1) ? -1 : 1;
coef *= comb.C(i, r);
f[i][j] += coef * C * g[i - r][j];
}
}
}
mint ans = 0;
for (int r = 1; r <= n; r ++) {
for (int c = 1; c <= m; c ++) {
mint coef = ((r + c) & 1) ? -1 : 1;
coef *= comb.C(n, r) * comb.C(m, c);
ans += coef * f[n - r][m - c] * C;
}
}
Write((int)ans, '\n');
return;
}
ARC193D Magnets
好难 /ll
对操作进行如下转化:在 \(A\) 的开头结尾各加一个 \(0\),然后选一个长度为 \(3\) 的连续子序列,将其替换为其最大值。
考虑如何判定进行 \(k\) 次操作能否将 \(A\) 变成 \(B\),这个问题就相当于在 \(A\) 的开头结尾各加 \(k\) 个 \(0\),能否将 \(A\) 划分成 \(n\) 段长度为奇数子序列,使得每段的最大值构成的序列恰好是 \(B\)。
这个可以贪心判定,如果 \(B_i\) 是 \(1\) 的话就找到最短的一段,否则找到极长的 \(0\) 的连续段,从 \(A\) 中切出一个包含这么多 \(0\) 的连续段来,一直这么进行下去,如果能划分完就合法。注意 \(B\) 中最后一个 \(1\) 的时候要把剩下的所有 \(1\) 都切出来。
我们可以对操作次数找一个下界 \(m\) 为 \(A\) 和 \(B\) 前后缀 \(0\) 的个数之差的较大值。
发现如果对于一个 \(m' \ge m + 2\),且 \(m'\) 可行,那么 \(m\) 一定可行,因为可以把多的两个 \(0\) 缩到第一个 \(1\) 连续段里。
所以只需判定 \(m\) 和 \(m + 1\) 即可,时间复杂度线性。
constexpr int N = 1e6 + 5;
char s[N], t[N];
void slv() {
int n = Read<int>();
vector<int> A(n), B(n);
Read(s), Read(t);
for (int i = 0; i < n; i ++) {
A[i] = s[i] - '0';
}
for (int i = 0; i < n; i ++) {
B[i] = t[i] - '0';
}
auto chk = [&](int n, int m, vector<int> A, vector<int> B) -> bool {
const int nA = n + m * 2, nB = n;
A.insert(A.begin(), m, 0);
A.insert(A.end(), m, 0);
vector<int> mx(nA), nxt(nA);
mx.back() = !A.back(), nxt.back() = nA - A.back();
for (int i = nA - 2; i >= 0; i --) {
mx[i] = !A[i] ? (mx[i + 1] + 1) : 0;
nxt[i] = A[i] ? i : nxt[i + 1];
}
int pre = -1, lst = nB;
while (!B[-- lst]);
for (int i = 0; i < nB; i ++) {
if (pre + 1 >= nA) {
return false;
}
if (B[i] == 1) {
int pos = nxt[pre + 1];
if (i == lst) {
while (pos + 1 < nA && nxt[pos + 1] != nA) {
pos = nxt[pos + 1];
}
}
if (pos >= nA) {
return false;
}
pre = pos + ((pre & 1) == (pos & 1));
} else {
int j = i;
while (j + 1 < nB && !B[j + 1]) {
++ j;
}
const int k = j - i + 1;
int pos = pre + 1;
while (pos < nA && mx[pos] < k) {
pos += 2;
}
if (pos >= nA) {
return false;
}
pre = pos + k - 1, i = j;
}
}
return true;
};
int lA = 0, rA = n, lB = 0, rB = n;
while (!A[lA ++]);
while (!A[-- rA]);
while (!B[lB ++]);
while (!B[-- rB]);
-- lA, rA = n - rA - 1;
-- lB, rB = n - rB - 1;
const int m = max({lB - lA, rB - rA, 0});
if (chk(n, m, A, B)) {
Write(m, '\n');
} else if (chk(n, m + 1, A, B)) {
Write(m + 1, '\n');
} else {
Puts("-1");
}
return;
}