「杂题乱写」AGC 002

「杂题乱写」AGC 002

点击查看目录

A | Range Product

分讨不解释。

B | Box and Ball

直接按题意模拟。

C | Knot Puzzle

不难发现解开最后一下仍然需要两个绳子长度 \(\ge l\),那么考虑找到两条相邻且长度满足条件的绳子作为最后一个解开的,然后从两边向中间解。

D | Stamp Rally

读错题了,我以为 \(u\) 必须走到 \(v\) . 恼了 .

「最大编号最小」遂考虑二分编号 .

似乎可以很自然的想到 Kruskal 重构树?不过当时板刷到这里时不会哇,题解区也看不懂 .

建出 Kruskal 重构树后,询问时两个点能往上跳就往上跳,然后查询子树大小是否有 \(z\) 个点 .

const int N = 1e5 + 10, P = 998244353;
namespace SOLVE {
	int n, m, q, tot, bfa[N];
	int fa[N][21], sz[N], val[N];
	std::vector <int> tu[N];
	inline int rnt () {
		int x = 0, w = 1; char c = getchar ();
		while (!isdigit (c)) { if (c == '-') w = -1; c = getchar (); }
		while (isdigit (c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar ();
		return x * w;
	}
	void DFS (int u) {
		_for (k, 1, 20)
			fa[u][k] = fa[fa[u][k - 1]][k - 1];
		far (v, tu[u]) {
			if (v == fa[u][0]) continue;
			fa[v][0] = u, DFS (v), sz[u] += sz[v];
		}
		return;
	}
	int Find (int x) { return bfa[x] == x ? x : bfa[x] = Find (bfa[x]); }
	int Check (int u, int v, int mid) {
		for_ (k, 20, 0) {
			if (val[fa[u][k]] <= mid) u = fa[u][k];
			if (val[fa[v][k]] <= mid) v = fa[v][k];
		}
		return u == v ? sz[u] : sz[u] + sz[v];
	}
	int Query (int u, int v, int k) {
		int l = 1, r = m;
		while (l <= r) {
			int mid = (l + r) >> 1;
			if (Check (u, v, mid) < k)
				l = mid + 1;
			else
				r = mid - 1;
		}
		return l;
	}
	inline void In () {
		tot = n = rnt (), m = rnt ();
		val[0] = m + 1;
		_for (i, 1, n) bfa[i] = i, sz[i] = 1;
		_for (i, 1, m) {
			int u = Find (rnt ()), v = Find (rnt ());
			if (u != v) {
				++tot;
				bfa[tot] = tot, val[tot] = i;
				bfa[u] = bfa[v] = tot;
				tu[tot].emplace_back (u);
				tu[tot].emplace_back (v);
			}
		}
		return;
	}
	inline void Solve () {
		DFS (tot);
		return;
	}
	inline void Out () {
		q = rnt ();
		_for (i, 1, q) {
			int u = rnt (), v = rnt (), k = rnt ();
			printf ("%d\n", Query (u, v, k));
		}
		return;
	}
}

E | Candy Piles

博弈论神仙题。

图都来源于官方题解。

首先对数组排序,然后可以把数组转化成这样:

(以 \(a = (7, 7, 7, 6, 4, 4, 4, 2, 2)\) 为例)

image

不难发现两种操作等价于删去一行/一列:

image

进一步转化一下,两种操作等价于在这个网格中向上/向右走一步:

image

先手从左下角走第一步,当到达边界的时候输掉。

那么可以在网格上画出必胜点(红色)和必败点(蓝色),即从这个点出发的先手是必胜还是必败:

image

不难发现先后手分别往左和往右走一步是否必胜不变,因此一条斜线上是否必胜都一样。

image

那么搞一个左下角为出发点的最大正方形,考虑判断这个正方形右上角是否为必胜点。

image

判断很好搞,因为此时只有纯向上走和纯向右走两个选择,所以如果有一种方法可以走奇数步则这个点是一个必胜点。

点击查看代码
const ll N = 1e5 + 10;
namespace SOLVE {
	ll n, a[N], k, ans;
	inline ll rnt () {
		ll x = 0, w = 1; char c = getchar ();
		while (!isdigit (c)) { if (c == '-') w = -1; c = getchar (); }
		while (isdigit (c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar ();
		return x * w;
	}
	inline void In () {
		n = rnt ();
		_for (i, 1, n) a[i] = rnt ();
		return;
	}
	inline void Solve () {
		std::sort (a + 1, a + n + 1, [](ll x, ll y) { return x > y; });
		_for (i, 1, n) if (i + 1 > a[i + 1]) { k = i; break; }
		ll l = 0;
		while (a[k + l + 1] == k) ++l;
		ans = (l & 1) || ((a[k] - k) & 1);
		return;
	}
	inline void Out () {
		puts (ans ? "First" : "Second");
		return;
	}
}

F | Leftmost Ball

DP 计数题。

\(f_{i, j}\) 表示放了 \(i\) 个白球,放了 \(j\) 种颜色的所有球的方案数。

考虑放一个白球,只能放在第一个空位,方案数 \(f_{i - 1, j}\)

考虑放一种颜色的所有球。首先有 \(n - j + 1\) 种颜色可以用,然后我们有 \(k - 2\) 个自由球可以放,一个球变成了白球,还有一个球为了保证不会被重复计算,必须放在第一个空位。 此时有 \(n * k - i - (j - 1)(k - 1) - 1\) 个空位,那么方案数为 \((n - j + 1)\dbinom{n * k - i - (j - 1)(k - 1) - 1}{k - 2}f_{i, j - 1}\)

点击查看代码
const ll N = 2e3 + 10, P = 1e9 + 7;
namespace SOLVE {
	ll n, k, f[N], fac[N * N], inv[N * N], ans;
	inline ll rnt () {
		ll x = 0, w = 1; char c = getchar ();
		while (!isdigit (c)) { if (c == '-') w = -1; c = getchar (); }
		while (isdigit (c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar ();
		return x * w;
	}
	inline ll FastPow (ll a, ll b) {
		ll ans = 1;
		while (b) {
			if (b & 1) ans = ans * a % P;
			a = a * a % P, b >>= 1;
		}
		return ans;
	}
	inline ll C (ll x, ll y) { return fac[x] * inv[y] % P * inv[x - y] % P; }
	inline void Pre () {
		fac[0] = 1;
		_for (i, 1, 4000000) fac[i] = fac[i - 1] * i % P;
		inv[4000000] = FastPow (fac[4000000], P - 2);
		for_ (i, 3999999, 0) inv[i] = inv[i + 1] * (i + 1) % P;
		return;
	}
	inline void In () {
		n = rnt (), k = rnt ();
		return;
	}
	inline void Solve () {
		Pre ();
		f[0] = 1;
		_for (i, 1, n) {
			_for (j, 1, i) {
				ll x = (n - j + 1) * C (n * k - i - (j - 1) * (k - 1) - 1, k - 2) % P * f[j - 1] % P;
				f[j] = (f[j] + x) % P;
			}
		}
		return;
	}
	inline void Out () {
		printf ("%lld\n", k == 1 ? 1 : f[n]);
		return;
	}
}
posted @ 2023-06-06 20:35  K8He  阅读(33)  评论(0编辑  收藏  举报