本场比赛最终结果不是很理想,可能题比较难。

A - CF189A

给一长度为 $n$ 的钢条,要求将其剪成若干长度为 $p,q,r$ 的短条,恰好裁完,且要求短条数量尽可能多。

分析

简单背包。

int main() {
	int n;
	vector<int> v(3);
	cin >> n >> v[0] >> v[1] >> v[2];
	vector<int> dp(n + 1, -1E9);
	dp[0] = 0;
	for (int vi : v) {
		for (int j = vi; j <= n; j++) {
			dp[j] = max(dp[j], dp[j - vi] + 1);
		}
	}
	cout << dp[n];
	return 0;
}

B - ARC100A

给定一个长为 $n$ 的整数序列 $\{a_i\}$,寻找一个整数 $x$ 使得下式最小:

$$ \sum_{i=1}^n |a_i - i - x| $$

分析

令 $b_i = a_i - i$,接下来只需使

$$ \sum_{i=1}^n |b_i - x| $$ 最小,显然取中位数即可。

int main() {
	int n;
	cin >> n;
	vector<int> v(n);
	for (int i = 0; i < n; i++) {
		cin >> v[i];
		v[i] -= i;
	}
	sort(v.begin(), v.end());
	ll ans = 0;
	for (int i = 0; i < n; i++) {
		ans += abs(v[i] - v[n / 2]);
	}
	cout << ans;
	return 0;
}

C - 601A

自己读题。

分析

注意到 $1 \to n$ 的通道要么是铁路,要么是公路,因此两种走法其中一种必然是 $1$,对另一种跑最短路即可。

// @description 二维数组(邻接矩阵)

struct Graph {
	using T = int;
	inline static const T inf = 1E9;
	int n;
	vector<T> m;
	Graph(int a) : n(a), m(n * n, inf) {
		for (int i = 0; i < n; i++)
			m[i * (n + 1)] = 0;
	}
	auto operator[](int i) {
		return m.begin() + i * n;
	}
	auto operator[](int i) const {
		return m.begin() + i * n;
	}
};

auto floyd(Graph f) {
	int n = f.n;
	for (int k = 0; k < n; k++) {
		for (int i = 0; i < n; i++) {
			for (int j = 0; j < n; j++) {
				f[i][j] = min(f[i][j], f[i][k] + f[k][j]);
			}
		}
	}
	return f;
}

int main() {
	int n, m;
	cin >> n >> m;

	Graph g1(n + 1), g2(n + 1);
	for (int i = 0; i < m; i++) {
		int u, v;
		cin >> u >> v;
		g1[u][v] = g1[v][u] = 1;
	}
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			if (g1[i][j] == 1)
				g2[i][j] = Graph::inf;
			else if (g1[i][j] == Graph::inf)
			 	g2[i][j] = 1;
		}
	}
	auto f1 = floyd(g1), f2 = floyd(g2);
	int ans = max(f1[1][n], f2[1][n]);
	cout << (ans <= n ? ans : -1);
	return 0;
}

D - ARC100B

将区间分成连续的 $4$ 份,求四个区间和之间的最小极差。

分析

首先,我们可以划分成四个区间 $[0, i]$,$[i+1, j]$,$[j + 1,k]$ 和 $[k+1,n]$,看起来需要 $O(n^3)$ ,但是注意到当我们决定好 $j$ 时,可以直接确定当 $i$ 和 $k$ 平分各自区间时,取到最优解。

因此可以三分(二分)找最优点,复杂度 $O(n \log n)$,也可以直接双指针,复杂度 $O(n)$。

template <class Func> // last true
ll lower(ll l, ll r, Func f) {
	while (r - l > 1) {
		ll m = l + (r - l) / 2;
		(f(m - 1) > f(m) ? l : r) = m;
	}
	return l;
}

int main() {
	int n;
	cin >> n;
	vector<ll> v(n + 1), sum(n + 1);
	for (int i = 1; i <= n; i++) {
		cin >> v[i];
		sum[i] = sum[i - 1] + v[i];
	}
	ll ans = 1E18;
	for (int i = 2; i <= n - 2; i++) {
		auto get = [&](int l, int r) {
			return sum[r] - sum[l - 1];
		};
		int x = lower(1, i + 1, [&](int m) {
			return abs(get(1, m) - get(m + 1, i));
		});
		int y = lower(i + 1, n + 1, [&](int m) {
			return abs(get(i + 1, m) - get(m + 1, n));
		});
		ll a = get(1, x), b = get(x + 1, i);
		ll c = get(i + 1, y), d = get(y + 1, n);
		ll tans = max({a, b, c, d}) - min({a, b, c, d});
		ans = min(ans, tans);
	}
	cout << ans;
	return 0;
}

E - CF687B

已知 $x \bmod c_i$ 的值,试问对于任意正整数 $x$ 能否得到 $x \bmod k$的值。

分析

根据中国剩余定理,问题等价于 $\operatorname{lcm}(\{c_i\}) \bmod m$ 是否为 $0$。

事实上证明也很容易想到,注意到

$$ x \bmod (ab) \bmod a = x \bmod a $$

因此我们可以対每个质数 $p$ 独立分析,不难推出我们至多保留 $c_i$ 中最大 $p^c$ 的信息,而不同的 $p$ 显然可以合并,因此最多即是 $\operatorname{lcm}(\{c_i\})$。

CPP17 之后头文件 <numeric> 中自带 std::lcm,可以很好的简化代码。

int main() {
	int n, k;
	cin >> n >> k;
	vector<ll> v(n);
	for (int i = 0; i < n; i++) {
		cin >> v[i];
	}
	ll ans = v[0];
	for (int i = 1; i < n; i++) {
		ans = std::lcm(ans, v[i]) % k;
	}
	cout << (ans % k == 0 ? "Yes" : "No");
	return 0;
}

F - CF432D

定义 $s$ 的完美子串为既是 $s$ 的前缀也是 $s$ 的后缀的子串,求所有完美子串及其在 $s$ 中的出现次数。

分析

KMP 板题,请认真理解 KMP 的 next 数组的定义。

auto pre_kmp(const string &s) {
	int n = s.length();
	vector<int> pi(n + 1);
	for (int i = 1; i < n; i++) {
		int j = pi[i];
		while (j > 0 && s[i] != s[j]) {
			j = pi[j];
		}
		pi[i + 1] = j + (s[i] == s[j]);
	}
	return pi;
}

int main() {
	string s;
	cin >> s;
	int n = s.length();
	auto z = pre_kmp(s);
	vector<int> v(n + 1, 1), ans;
	for (int i = n; i >= 1; i = z[i]) {
		ans.push_back(i);
	}
	for (int i = n; i >= 1; i--) {
		v[z[i]] += v[i];
	}
	std::sort(ans.begin(), ans.end());
	cout << ans.size() << '\n';
	for (int ai : ans) {
		cout << ai << ' ' << v[ai] << '\n';
	}
	return 0;
}

G - CF1285D

找到满足条件的 $x$,使

$$ \max_{1\leqslant i\leqslant n}(a_i \oplus X) $$

最小,输出最小值。

分析

非常经典的字典树维护最大值,对于第 $j$ 位,

  • 若 $a_i$ 只存在 $1$,那么 $x$ 第 $j$ 位应是 $1$。
  • 若 $a_i$ 只存在 $0$,那么 $x$ 第 $j$ 位应是 $0$。
  • 若 $a_i$ 既有 $0$ 也有 $1$,那么 $x$ 的第 $j$ 位不管是啥都会产生 $2^j$ 的贡献,但是并不确定其对后续结果的影响,需要进行递归求解。
template <int N>
struct Trie01 {
	int cnt = 1;
	vector<array<int, 2>> tr;
	Trie01(int n) : tr(n) {}
	void insert(int x) {
		int p = 1;
		for (int i = N - 1; i >= 0; i--) {
			int xi = (x >> i) & 1;
			int &tp = tr[p][xi];
			if (tp == 0)
				tp = ++cnt;
			p = tp;
		}
	}
	int solve(int x = 0, int j = N - 1, int p = 1) {
		if (p > 0 and j >= 0) {
			const int &ls = tr[p][0], &rs = tr[p][1];
			int vl = solve(x | 0 << j, j - 1, ls);
			int vr = solve(x | 1 << j, j - 1, rs);
			if (ls == 0 and rs == 0) {
				return x & 1;
			} else if (ls != 0 and rs == 0) {
				return vl;
			} else if (ls == 0 and rs != 0) {
				return vr;
			} else {
				return min(vl, vr) | 1 << j;
			}
		} else {
			return 0;
		}
	}
};

int main() {
	int n;
	cin >> n;
	Trie01<30> tr(n * 32);
	for (int i = 0; i < n; i++) {
		int x;
		cin >> x;
		tr.insert(x);
	}
	cout << tr.solve();

	return 0;
}

H - CF245H

给定一个字符串 $s$, 询问区间 $[l,r]$ 内的回文串个数。

分析

很经典的区间 DP。设 ${\rm DP}(i, j)$ 表示区间 $[i, j]$ 的回文串数量,那么用容斥定理可以转移

$$ {\rm DP}(i,j) = {\rm DP}(i,j-1) + {\rm DP}(i+1, j) - {\rm DP}(i+1,j-1) + {\rm IS}(i,j) $$

其中 ${\rm IS}(i, j)$ 为区间 $[i,j]$ 是否回文。

int main() {
	string s;
	int Q;
	cin >> s >> Q;
	int n = s.length();
	VV<int> is(n, n), dp(n, n);
	for (int len = 1; len <= n; len++) {
		for (int l = 0, r = l + len - 1; r < n; l++, r++) {
			if (len == 1) {
				dp[l][r] = is[l][r] = 1;
			} else if (len == 2) {
				is[l][r] = s[l] == s[r];
				dp[l][r] = 2 + is[l][r];
			} else {
				is[l][r] = is[l + 1][r - 1] and s[l] == s[r];
				dp[l][r] = dp[l][r - 1] + dp[l + 1][r] - dp[l + 1][r - 1] + is[l][r];
			}
		}
	}
	while (Q--) {
		int l, r;
		cin >> l >> r;
		cout << dp[l - 1][r - 1] << '\n';
	}
	return 0;
}