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}\)

这样我们就得到了转移:

\[\begin{aligned} f_{i, j} &= \sum_{1 \le x \le i} (-1)^{x+1} \binom{i}{x}C^{x} f_{i - x, j} \\ &+ \sum_{1 \le y \le j} (-1)^{y + 1} \binom{j}{y} C^{y} f_{i, j - y} \\ &+ \sum_{1 \le x \le i} \sum_{1 \le y \le j} (-1)^{x + y + 1} \binom{i}{x} \binom{j}{y} C f_{i - x, j - y} \end{aligned} \]

这样就能在 \(O(n^4)\) 的时间复杂度内得到 \(f\)

统计答案就是上面的第三种情况要求贡献为 \(1\),所以令容斥系数 \(c_{x, y} = (-1)^{x + y}\) 即可得到:

\[ans = \sum_{1 \le i \le n} \sum_{1 \le j \le m} (-1)^{i + j} \binom{n}{i} \binom{m}{j} C f_{n - i, m - j} \]

这样就得到了 \(O(n^4)\) 的做法。

瓶颈在于同时钦定行列的情况的转移,也就是计算:

\[\sum_{1 \le x \le i} \sum_{1 \le y \le j} (-1)^{x +y +1} \binom{i}{x} \binom{j}{y} Cf_{i - x, j - y} \]

\(\displaystyle g_{i, j} = \sum_{1 \le y \le j} \binom{j}{y} (-1)^{y + 1} f_{i, j - y}\),那么转移就变成了:

\[\begin{aligned} f_{i, j} &= \sum_{1 \le x \le i} (-1)^{x+1} \binom{i}{x}C^{x} f_{i - x, j} \\ &+ \sum_{1 \le y \le j} (-1)^{y + 1} \binom{j}{y} C^{y} f_{i, j - y} \\ &+ \sum_{1 \le x \le i} (-1)^x \binom{i}{x} C g_{i - x, j} \\ g_{i, j} &= \sum_{1 \le y \le j} \binom{j}{y} (-1)^{y + 1} f_{i, j - y} \end{aligned} \]

这样就做到了 \(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;
}
posted @ 2025-03-13 10:55  definieren  阅读(131)  评论(0)    收藏  举报