240906 说不上爱别说谎

盒盒盒。这歌居然是 16 年的,都过了七八年了,突然感觉自己好老(?)

感觉自己最近说话越来越像什么,cache 命中率极低且错位。我吹过你吹过的晚风~

cache 怎么念。ca - 卡,che - 车,卡车。


A. Leftmost Ball

https://atcoder.jp/contests/agc002/tasks/agc002_f

这玩意儿不难想到,相当于是给你 \(n\) 个白球和 \(n\) 种其他颜色的球,每种 \(k-1\) 个;要求对于任意前缀,白球个数 \(\ge\) 其他颜色总数的方案数。

然后就愣住了。不知道这玩意儿怎么 DP。怒贺题解之后发现新大陆。

\(f_{i, j}\) 表示目前选了 \(i\) 个白球,选\(j\) 种其它颜色的方案数。那么肯定有 \(i\ge j\)。然后注意到状态里面是不包含位置要素的,这是因为加上了位置反而需要额外记录每种颜色选了多少个,得不偿失;不加位置状态也能转移。只需要关注还没选的 \(n-i-j\times (k-1)\) 个位置就行了。注意这些空位之间是有相对顺序的,总之挺符合直觉。

那么转移就是放白球(在最前面),和放另一个新颜色(要求第一个在最前面)。显然有:

\[f_{i+1,j}\gets f_{i+1,j}+f_{i,j},\\ f_{i, j+1}\gets f_{i, j+1}+f_{i,j}\times C_{n-j}^1\times C_{n\times k-i-j\times(k-1)-1}^{(k-1)-1}. \]

二者均须保证选掉第一个空位是为了保证不重不漏。

初始化 \(f_{0,0}=1\),答案即为 \(f_{n,n}\)。注意 \(k=1\) 要判一下。

#include <bits/stdc++.h>
const int mod = 1e9 + 7;
int main() {
#ifdef ONLINE_JUDGE
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	std::cout.tie(nullptr);
#else
	freopen(".in", "r", stdin);
	freopen(".out", "w", stdout);
#endif
	int n, k;
	std::cin >> n >> k;
	if (k == 1) {
		std::cout << 1 << '\n';
		return 0;
	}
	std::vector<std::vector<long long> > f(n + 1, std::vector<long long> (n + 1));
	std::vector<long long> fac(n * k + 1), inv(n * k + 1);
	fac[0] = inv[0] = 1;
	auto qkp = [](long long x, int y) {
		long long res = 1;
		for (; y; (x *= x) %= mod, y >>= 1)
			if (y & 1)
				(res *= x) %= mod;
		return res;
	};
	fac[0] = inv[0] = 1ll;
	for (int i = 1; i <= n * k; ++i) {
		fac[i] = fac[i - 1] * i % mod;
		inv[i] = qkp(fac[i], mod - 2);
	}
	auto C = [&](int n, int m) {
		return fac[n] * inv[n - m] % mod * inv[m] % mod;
	};
	f[0][0] = 1;
	for (int i = 0; i <= n; ++i)
		for (int j = 0; j <= n; ++j) {
			if (i + 1 <= n)
				(f[i + 1][j] += f[i][j]) %= mod;
			if (j + 1 <= n && i >= j + 1)
				(f[i][j + 1] += f[i][j] * (n - j) % mod * C(n * k - i - j * (k - 1) - 1, k - 2) % mod) %= mod;
		}
	std::cout << f[n][n] << '\n';
	return 0;
}

F1. Small Permutation Problem (Easy Version)

https://codeforces.com/problemset/problem/1909/F1

看到排列,考虑转化为棋盘上放车。则原问题转化为,对于任意一个 \(i\),满足在矩形左上方 \((i-1)\times (i-1)\) 的子矩阵中共有 \(a_i\) 个车。

考虑从 \(i-1\to i\) 新增的观察范围,为包含 \(2\times i-1\) 个元素的 L 形。而每次新观察到的元素,要么来自于前 \(i-1\) 列,位于第 \(i\) 行(易知这样的元素最多只有一个),要么来自于第 \(i\) 列,位于前 \(i\) 行。

我们的任务是向第 \(i\) 列中填数,使得在左上角 \(i\times i\) 的矩形中有 \(a_i\) 个车。而已知左上角 \((i-1)\times(i-1)\) 的矩形中有 \(a_{i-1}\) 个车,我们分讨一下:

  1. \(a_i=a_{i-1}\):L 形区域内没有车。
  2. \(a_i=a_{i-1}+1\):L 形区域内有一个车,即可以来自前 \(i-1\) 列,也可以来自第 \(i\) 列。
  3. \(a_i = a_{i - 1} + 2\):L 形区域内有两个车,须满足一个在第 \(i\) 行,另一个在第 \(i\) 列,且此时 \((i,i)\) 不可选。
  4. Otherwise:无解。

但是具体怎么 DP 呢?我们参考 放棋子 中的做法,既然已经知道在前 \(i-1\) 行、列都已经被其他车占用了 \(a_{i-1}\) 个,那么我们当前观察的 \(\le 2\) 个车可选择的行 / 列均已经只剩下 \(i-a_{i-1}\)

还有一个很神的地方,就是我们注意到如果记录的是前 \(i\) 列的方案数,那么转移时势必需要位置信息;但如果我们只记录前 \(i\) 列前 \(i\) 行的方案数,就可以很方便地转移。这样乍一看不太对劲,不在前 \(i\) 行中的车感觉会很奇怪;但实际上会在 \(i\) 到达其行数的时候被记入。

故而我们有:

\[f_i= \begin{cases} f_{i-1}&a_i=a_{i-1}\\ f_{i-1}\times C_{(2\times i - 1)-2\times a_{i-1}}^1&a_i=a_{i-1}+1\\ f_{i-1}\times C_{i-a_{i-1}-1}^1\times C_{i-a_{i-1}-1}^1&a_i=a_{i-1}+2\\ 0&\text{otherwise} \end{cases} . \]

在上述条件的约束下,只需要特判 \(a_n\ne n\) 时无解即可。

#include <bits/stdc++.h>
const int mod = 998244353;
int main() {
#ifdef ONLINE_JUDGE
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	std::cout.tie(nullptr);
#else
	freopen(".in", "r", stdin);
	freopen(".out", "w", stdout);
#endif
	int T;
	for (std::cin >> T; T--; ) {
		int n;
		std::cin >> n;
		std::vector<int> a(n + 1);
		std::vector<long long> f(n + 1);
		f[0] = 1;
		for (int i = 1; i <= n; ++i) {
			std::cin >> a[i];
			if (a[i] == a[i - 1])
				f[i] = f[i - 1];
			else if (a[i] == a[i - 1] + 1)
				f[i] = f[i - 1] * (2 * i - 1 - 2 * a[i - 1]) % mod;
			else if (a[i] == a[i - 1] + 2)
				f[i] = f[i - 1] * (i - a[i - 1] - 1) % mod * (i - a[i - 1] - 1) % mod;
			else
				f[i] = 0;
		}
		std::cout << (a[n] == n ? f[n] : 0) << '\n';
	}
	return 0;
}

F2. Small Permutation Problem (Hard Version)

https://codeforces.com/problemset/problem/1909/F2

posted @ 2024-09-26 11:34  XSC062  阅读(48)  评论(1编辑  收藏  举报