AT AGC002 题解

A

简单分类讨论题,没什么意思。

#include <bits/stdc++.h>

using i64 = long long;

void solve() {
	i64 a, b;
	std::cin >> a >> b;
	if (a == 0 || b == 0) {
		std::cout << "Zero\n";
		return;
	}
	if (a > 0) {
		if (b > 0)
			std::cout << "Positive\n";
		else
			std::cout << "Zero\n";
	} else {
		if (b > 0)
			std::cout << "Zero\n";
		else
			std::cout << (std::abs(a - b) & 1 ? "Positive" : "Negative") << "\n";
	}
}

int main() {
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);

	solve();
	return 0;
}

B

简单模拟题。拿桶维护一下,顺便统计一下颜色的可行性?做完了。

#include <bits/stdc++.h>

using i64 = long long;

void solve() {
	int n, m;
	std::cin >> n >> m;
	std::vector<int> cnt(n + 1, 1), clr(n + 1);
	int ans = 1; clr[1] = 1;
	for (int i = 1, x, y; i <= m; i++) {
		std::cin >> x >> y;
		cnt[y]++, cnt[x]--;
		if (clr[x]) {
			if (!clr[y])
				ans++;
			clr[y] = 1;
		}
		if (!cnt[x]) {
			if (clr[x]) {
				clr[x] = 0;
				ans--;
			}
		}
	}
	std::cout << ans << "\n";
}

int main() {
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);

	solve();
	return 0;
}

C

贪心 pick 左右两边较小值 pop 想一想都知道很容易被 hack,套路化的,考虑转构造为判定。只有剩下最后两段绳子的长度 \(\geq l\) 才会有解,找到这两段绳子然后从两端向中间 pop 记录答案就可以了。

#include <bits/stdc++.h>

using i64 = long long;

void solve() {
	int n, k;
	std::cin >> n >> k;
	std::vector<int> a(n + 1);
	for (int i = 1; i <= n; i++) {
		std::cin >> a[i];
	}
	for (int i = 1; i < n; i++) {
		if (a[i] + a[i + 1] >= k) {
			std::cout << "Possible\n";
			for (int j = 1; j < i; j++)
				std::cout << j << "\n";
			for (int j = n; j >= i + 2; j--)
				std::cout << j - 1 << "\n";
			std::cout << i << "\n";
			return;
		}
	}
	std::cout << "Impossible\n";
}

int main() {
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);

	solve();
	return 0;
}

D

好题。这是第几次学 Kruskal 重构树了?

显然第一步是二分最大的边权,考虑验证。裸的复杂度是 \(O(qn \log n)\) 的,对于这张图,仿照 Kruskal 生成树的思路建 MST,在求 MST 的过程中对于当前边所连接的两个点(或者已经完成连接的点集,见下)连一个虚拟节点作为祖先,将祖先的权赋值为边权。重复这个过程合并节点直到选出了 \(2n - 1\) 条边。在这个树上每条链中边权单调,可以通过倍增来求解最大不超过某个值的最小位置。对于这个题就是我们二分答案 \(x\),分别向上跳到边权大于 \(x\) 的值为止,然后向下子树中叶子节点 + 1就是经过节点的个数。

实现上,由于边权即为编号 \(i\),我们直接截取前 \(2n - 1\) 的边即可,少了一个排序。

#include <bits/stdc++.h>

using i64 = long long;

constexpr int N = 1e5 + 7;
constexpr int M = 2e5 + 7;

int n, m, q;
int fa[M][30], siz[M], val[M];

std::vector<int> adj[M];

struct DSU {
	int fa[M];

	void init(const int _n) {
		for (int i = 1; i <= _n; i++) {
			fa[i] = i;
		}
	}

	int find(int x) {
		return (fa[x] == x ? x : fa[x] = find(fa[x]));
	}
} uni;

void dfs(int u, int from) {
	fa[u][0] = from;
	if (u <= n) { siz[u] = 1; return; }
	siz[u] = 0;
	for (auto v : adj[u]) {
		dfs(v, u);
		siz[u] += siz[v];
	}
}

void init(int u) {
	dfs(u, 0);
	val[0] = m + 1;
	for (int j = 1; j < 20; j++) {
		for (int i = 1; i <= u; i++)
			fa[i][j] = fa[fa[i][j - 1]][j - 1];
	}
}

int check(int k, int u, int v) {
	for (int i = 19; ~i; i--) {
		if (val[fa[u][i]] <= k)
			u = fa[u][i];
		if (val[fa[v][i]] <= k)
			v = fa[v][i];
	}
	if (u == v)
		return siz[u];
	return siz[u] + siz[v];
}

int main() {
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);

	std::cin >> n >> m;
	uni.init(n * 2); int ncnt = n;
	for (int i = 1; i <= m; i++) {
		int u, v; std::cin >> u >> v;
		u = uni.find(u), v = uni.find(v);
		if (ncnt < n * 2 - 1 && u != v) {
			ncnt++;
			val[ncnt] = i;
			uni.fa[u] = uni.fa[v] = ncnt;
			adj[ncnt].push_back(u);
			adj[ncnt].push_back(v);
		}
	}
	init(ncnt);
	std::cin >> q;
	while (q--) {
		int u, v, z; std::cin >> u >> v >> z;
		int l = 1, r = m;
		while (l < r) {
			int mid = (l + r) >> 1;
			if (check(mid, u, v) >= z)
				r = mid;
			else
				l = mid + 1;
		}
		std::cout << l << "\n";
	}
	return 0;
}

E

博弈论好题,经典的博弈论放到坐标系上走的思路,可惜确实没什么经验去想到。

由于操作中要求我们选择糖果中最多的一堆或者全部各取一个吃掉,所以我们要从最多的堆入手出发才能考虑怎么处理操作 1。从大到小排序。

将排列后的数组看作列高,第 \(i\) 列有 \(a_i\) 个格子,操作一等于吃掉最左边最高的一列,操作二等于吃掉最下面一行,在坐标系中等同于向右或者向上走,我们从左下角出发,有两种操作方向,走到边界即 \(i \gt n\) 或者 \(j \gt a_i\) 时必败,变成了一个博弈组合问题。

考虑一个公平游戏的处理,对于某个状态必胜当且仅当其能转移来的两个状态中有一个必败一个必胜,一个状态为必败当且仅当能转移来的两个状态均为必胜。

如果对这个矩阵图进行先手的 W/L 分析会发现呈现一条类似对角线的情况分布,实现中,我们去检查每个节点所在对角线的某个节点的情况就可以了。具体地,找到最大的等长矩形,向上、向右找到离边界的距离,如果有一个为奇数则为不合法,否则合法必胜。

#include <bits/stdc++.h>

using i64 = long long;

void solve() {
	int n;
	std::cin >> n;
	std::vector<int> a(n + 1);
	for (int i = 1; i <= n; i++) {
		std::cin >> a[i];
	}
	std::sort(a.begin() + 1, a.end(), std::greater<int>());
	for (int i = 1; i <= n; i++) {
		if (a[i + 1] < i + 1) {
			int j = 0;
			while (a[j + i + 1] == i)
				j++;
			if (((a[i] - i) & 1) || (j & 1))
				std::cout << "First\n";
			else
				std::cout << "Second\n";
			break;
		}
	}
}

int main() {
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);

	solve();
	return 0;
}

F

真的学不会计数。考虑 DP,由于我们注意到最终会摆放 \(n\) 个白球,\(n\) 种其他颜色的球各 \(k - 1\) 个。Another key observation,任意前缀中白球的个数都大于等于其他颜色的种类数时合法。将白球和其他颜色的球分开来?

\(f(i, j)\) 表示已经放了 \(i\) 个白球和 \(j\) 种其他颜色的可行方案数,其中 \(j \leq i\)为了去重,我们钦定所有放入的元素都从从左到右第一个空位开始放。考虑分放入的颜色来转移。

放白球时,无论怎么丢都是合法的,我们直接继承 \(f(i - 1, j)\) 的答案。

放新颜色时从 \(f(i, j - 1)\) 转移过来,从剩下的 \(n - (j - 1)\) 种颜色中选出在之前没有被放置过的一个新的颜色作为新球,放在第一个空位,此时钦定的好处就体现出来了,无论我们怎么丢,由于白球都尽可能地先安排到了前面的空位,只要我们保持 \(j \leq i\) 则新放入的颜色球必定合法。放完后还剩下 \(k - 1 - 1 = k - 2\) 个此颜色的球没有放,在剩余的空位中任意找 \(k - 2\) 个空位放入,即 \(\binom{nk - i - (j - 1)(k - 1) - 1}{k - 2}\),得到转移如下:

\[f(i, j) = f(i - 1, j) + f(i, j - 1) \times (n - j + 1)\binom{nk - i - (j - 1)(k - 1) - 1}{k - 2} \]

边界为 \(f(i, 0) = 1, i \in [1, n]\),即前 \(i\) 个位置全放白球的方案数显然为 \(1\)

特判掉 \(k = 1\) 时答案始终为 \(1\)

#include <bits/stdc++.h>

using i64 = long long;

constexpr int N = 2007;
constexpr int V = 4000007;
constexpr int P = 1e9 + 7;

int n, k;
int fac[V], inv[V];
i64 dp[N][N];

template <typename T>
void modadd(T &x, T y) { x = (x + y >= P ? x + y - P : x + y); }

template <typename T>
T expow(T a, T b) {
	T res = 1;
	for (; b; b >>= 1) {
		if (b & 1)
			res = 1ll * res * a % P;
		a = 1ll * a * a % P;
	}
	return res;
}

void init(const int _n) {
	fac[0] = 1;
	for (int i = 1; i <= _n; i++) {
		fac[i] = 1ll * fac[i - 1] * i % P;
	}
	inv[_n] = expow(fac[_n], P - 2);
	for (int i = _n; i; i--) {
		inv[i - 1] = 1ll * inv[i] * i % P;
	}
}

int C(int n, int m) {
	if (n < 0 || m < 0 || n < m)
		return 0;
	return (1ll * fac[n] * inv[m] % P * inv[n - m] % P);
}

int main() {
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);

	std::cin >> n >> k;
	if (k == 1) { std::cout << "1\n"; exit(0); }
	init(4e6);
	for (int i = 1; i <= n; i++) {
		dp[i][0] = 1;
	}
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= i; j++) {
			modadd(dp[i][j], (dp[i - 1][j] + 1ll * dp[i][j - 1] * (n - j + 1) % P * C(n - i + (n - j + 1) * (k - 1) - 1, k - 2) % P) % P);
		}
	}
	std::cout << dp[n][n] << "\n";
	return 0;
}
posted @ 2025-11-15 11:32  夢回路  阅读(3)  评论(0)    收藏  举报