SetAndSet
题目描述
给 $n$ 个数 $a_1, a_2, \cdots, a_n$,要把它们划分成两个集合,使得两边的 And 一样,问方案数。
数据范围:$n \le 50, 0 \le a_i < 2^{20}$。
思路分析
也就是说对于每一位,如果存在一个数这一位为 $0$,那么这一位为 $0$ 的数不能全在同一边。考虑容斥,每次强制某些位让这一位为 $0$ 的数全在同一边。使用并查集维护即可,直接实现时间复杂度 $O(2^{20} \times 20n)$,容斥用 dfs 实现复杂度为 $O(2^{20} n)$,可以通过(忽略并查集复杂度)。
代码实现
1 |
|
2 | using namespace std; |
3 | |
4 | typedef long long ll; |
5 | const int maxn = 50, maxm = 20; |
6 | |
7 | class SetAndSet { |
8 | public: |
9 | int n, a[maxn + 3], fa[maxm + 3][maxn + 3]; |
10 | vector<int> S[maxm + 3]; |
11 | bool vis[maxn + 3]; |
12 | ll ans; |
13 | |
14 | int find(int i, int x) { |
15 | return fa[i][x] == x ? x : fa[i][x] = find(i, fa[i][x]); |
16 | } |
17 | |
18 | void dfs(int i, int x) { |
19 | if (i == maxm) { |
20 | for (int j = 1; j <= n; j++) { |
21 | vis[j] = false; |
22 | } |
23 | for (int j = 1; j <= n; j++) { |
24 | vis[find(i, j)] = true; |
25 | } |
26 | ll y = 1; |
27 | for (int j = 1; j <= n; j++) { |
28 | if (vis[j]) y <<= 1; |
29 | } |
30 | ans += x * (y - 2); |
31 | return; |
32 | } |
33 | memcpy(fa[i + 1], fa[i], sizeof(fa[i])); |
34 | dfs(i + 1, x); |
35 | if (!S[i].empty()) { |
36 | memcpy(fa[i + 1], fa[i], sizeof(fa[i])); |
37 | for (int j = 0; j < S[i].size() - 1; j++) { |
38 | fa[i + 1][find(i + 1, S[i][j])] = find(i + 1, S[i][j + 1]); |
39 | } |
40 | dfs(i + 1, -x); |
41 | } |
42 | } |
43 | |
44 | ll countandset(vector<int> A) { |
45 | n = A.size(); |
46 | for (int i = 1; i <= n; i++) { |
47 | a[i] = A[i - 1]; |
48 | for (int j = 0; j < maxm; j++) { |
49 | if (~a[i] >> j & 1) { |
50 | S[j].push_back(i); |
51 | } |
52 | } |
53 | } |
54 | for (int i = 1; i <= n; i++) { |
55 | fa[0][i] = i; |
56 | } |
57 | ans = 0; |
58 | dfs(0, 1); |
59 | return ans; |
60 | } |
61 | }; |
Endless Spin
题目描述
有 $n$ 个格子,每次随机选择一个区间染黑,问全染黑的期望时间。答案保留 $15$ 位小数。
数据范围:$T, n \le 50$。
思路分析
我们要求的是 $\max(T_1, T_2, \cdots, T_n)$,其中 $T_i$ 表示第 $i$ 个格子被覆盖的时间。考虑 Min-Max 容斥,计算 $\sum_{S \in [n]} (-1)^{\vert S \vert - 1} \min_{i \in S}(T_i)$。对于某个 $S$,我们要算出经过 $S$ 种某个点的区间个数 $x$,设总区间个数为 $y$,那么它对答案的贡献是容斥系数乘上 $\frac{y}{x}$。带上容斥系数套个 dp 即可,由于精度要求较高可以使用高精度 / int128,时间复杂度 $O(n^4)$。
代码实现
1 |
|
2 | using namespace std; |
3 | |
4 | typedef long long ll; |
5 | typedef __int128 lll; |
6 | typedef double db; |
7 | const int maxn = 50, maxm = maxn * (maxn + 1) >> 1; |
8 | const ll inf = 1e18; |
9 | ll dp[maxn + 3][maxm + 3], f[maxn + 3][maxm + 3]; |
10 | lll ans[maxn + 3]; |
11 | int T; |
12 | |
13 | inline int func(const int &x) { |
14 | return x * (x + 1) >> 1; |
15 | } |
16 | |
17 | int main() { |
18 | for (int i = 1; i <= maxn; i++) { |
19 | dp[i][func(i - 1)] = 1; |
20 | for (int j = 1; j < i; j++) { |
21 | for (int k = 0; k <= func(j - 1); k++) { |
22 | dp[i][func(i - j - 1) + k] -= dp[j][k]; |
23 | } |
24 | } |
25 | } |
26 | for (int i = 1; i <= maxn; i++) { |
27 | for (int j = 1; j <= i; j++) { |
28 | for (int k = 0; k <= func(j - 1); k++) { |
29 | f[i][func(i - j) + k] += dp[j][k]; |
30 | } |
31 | } |
32 | } |
33 | for (int i = 1; i <= maxn; i++) { |
34 | for (int j = 0; j <= func(i - 1); j++) { |
35 | ans[i] += (lll) f[i][j] * func(i) * inf / (func(i) - j); |
36 | } |
37 | } |
38 | scanf("%d", &T); |
39 | for (int n; T --> 0; ) { |
40 | scanf("%d", &n); |
41 | printf("%d.%015lld\n", (int) (ans[n] / inf), ((ll) (ans[n] % inf) + 500) / 1000); |
42 | } |
43 | return 0; |
44 | } |
随机立方体
题目描述
有 $n \times m \times l$ 的立方体,每个位置有 $[0, 1]$ 范围内随机的实数,一个数是极大的当且仅当它大于任何一个与它某一维坐标相同的数。问恰好有 $k$ 个极大的数的概率。
数据范围:$T \le 10, n, m, l \le 5 \times 10^6, k \le 100$。
思路分析
记 $N = \min(n, m, l)$,以及钦定 $i$ 个数极大,其他随便选的概率的和为 $A_i$,那么根据二项式反演,答案等于:
我们考虑 $A_i$ 怎么求。首先从小到大钦定 $i$ 个数,之后我们会得到一棵表示大小关系的树:
上图是二维时 $n = 4, m = 3, i = 2$ 的情况,两个极大值分别是 $(1, 1)$ 和 $(2, 2)$,一个格子指向另一个格子表示一个格子大于另一个格子。根据经典结论,概率应该是子树大小的倒数相乘,所以最终的式子是:
直接计算即可,注意要用到线性求逆元,时间复杂度 $O(Tn)$。
代码实现
1 |
|
2 | using namespace std; |
3 | |
4 | const int maxn = 5e6, mod = 998244353; |
5 | int T, n, m, l, k, N, fact[maxn + 3], finv[maxn + 3], a[maxn + 3]; |
6 | |
7 | int qpow(int a, int b) { |
8 | int c = 1; |
9 | for (; b; b >>= 1, a = 1ll * a * a % mod) { |
10 | if (b & 1) c = 1ll * a * c % mod; |
11 | } |
12 | return c; |
13 | } |
14 | |
15 | void prework(int n) { |
16 | fact[0] = 1; |
17 | for (int i = 1; i <= n; i++) { |
18 | fact[i] = 1ll * fact[i - 1] * i % mod; |
19 | } |
20 | finv[n] = qpow(fact[n], mod - 2); |
21 | for (int i = n; i; i--) { |
22 | finv[i - 1] = 1ll * finv[i] * i % mod; |
23 | } |
24 | } |
25 | |
26 | int C(int n, int m) { |
27 | return 1ll * fact[n] * finv[m] % mod * finv[n - m] % mod; |
28 | } |
29 | |
30 | void solve(int a[], int n) { |
31 | int prod = 1; |
32 | for (int i = 1; i <= n; i++) { |
33 | prod = 1ll * prod * a[i] % mod; |
34 | } |
35 | int inv = qpow(prod, mod - 2); |
36 | for (int i = n, x; i; i--) { |
37 | x = a[i], a[i] = inv; |
38 | inv = 1ll * inv * x % mod; |
39 | } |
40 | } |
41 | |
42 | int main() { |
43 | prework(maxn); |
44 | scanf("%d", &T); |
45 | while (T --> 0) { |
46 | scanf("%d %d %d %d", &n, &m, &l, &k); |
47 | N = min(n, min(m, l)); |
48 | for (int i = 1; i <= N; i++) { |
49 | a[i] = (1ll * n * m % mod * l - 1ll * (n - i) * (m - i) % mod * (l - i)) % mod; |
50 | a[i] < 0 ? a[i] += mod : 0; |
51 | } |
52 | solve(a, N); |
53 | int cur = 1; |
54 | for (int i = 1; i < k; i++) { |
55 | cur = 1ll * cur * (n - i + 1) % mod * (m - i + 1) % mod * (l - i + 1) % mod; |
56 | } |
57 | int ans = 0; |
58 | for (int i = k; i <= N; i++) { |
59 | cur = 1ll * cur * (n - i + 1) % mod * (m - i + 1) % mod * (l - i + 1) % mod; |
60 | ans = (ans + 1ll * C(i, k) * ((i - k) & 1 ? -1 : 1) * cur % mod * a[i]) % mod; |
61 | } |
62 | ans < 0 ? ans += mod : 0; |
63 | printf("%d\n", ans); |
64 | } |
65 | return 0; |
66 | } |
Square Constraints
题目描述
求有多少个 $0, 1, \cdots, 2n - 1$ 的排列 $p$,满足 $n^2 \le i^2 + p_i^2 \le 4n^2$。
数据范围:$n \le 250$。
思路分析
考虑子问题:长度为 $0, 2, \cdots, m - 1$ 的排列 $p$ 有多少个满足 $p_i < a_i$。考虑先把 $a_i$ 从小到大排序,根据乘法原理,方案数就是 $\prod_{i = 0}^{m - 1} (a_i - i)$。
注意到这个做法是基于 $a_i$ 的大小排名的。考虑原题,发现限制的形状如下:
阴影部分是 $(i, p_i)$ 可以选的地方,空白部分是不能选的地方。我们考虑容斥,把前 $n$ 个限制拆成 $[p_i \le r_i] - [p_i \le l_i - 1]$。考虑把所有限制分成三类:A 类,B 类和 C 类。我们把所有限制按照高度从小到大排序,得到的序列一定长成这样:$\text{ACACAACACCBBBBB}$。在序列中,我们要选择所有的 C,以及第 $i$ 个 A 和第 $i$ 个 B 中的一个。额外地,每选到一个 A 后,系数就要乘上 $-1$。
考虑 dp 前 $2n$ 个字符,$f(i, j)$ 表示考虑前 $i$ 个字符,A 选了 $j$ 个的方案数。对于一个 A / C,我们容易计算它的排名,但是对于一个 A 对应的 B,我们就不能得到它的排名了。于是,我们考虑事先枚举要用 $k$ 个 A,然后再 dp,这样就可以得到 B 的排名。这样我们就得到了一个时间复杂度为 $O(n^3)$ 的做法。
代码实现
1 |
|
2 |
|
3 |
|
4 | using namespace std; |
5 | |
6 | const int maxn = 500; |
7 | int n, mod, L[maxn + 3], R[maxn + 3], dp[maxn + 3][maxn + 3]; |
8 | pair<int, int> a[maxn + 3]; |
9 | |
10 | int get(int x, int y) { |
11 | int z = 0; |
12 | while (z < n * 2 - 1 && (z + 1) * (z + 1) + x <= y) { |
13 | z++; |
14 | } |
15 | return z; |
16 | } |
17 | |
18 | int solve(int k) { |
19 | memset(dp, 0, sizeof(dp)); |
20 | dp[0][0] = 1; |
21 | int A = 0, C = 0; |
22 | for (int i = 0; i < n * 2; i++) { |
23 | if (~a[i].se) { |
24 | for (int j = 0; j <= A; j++) { |
25 | dp[i + 1][j + 1] = (dp[i + 1][j + 1] + 1ll * dp[i][j] * (a[i].fi - j - C)) % mod; |
26 | } |
27 | for (int j = 0; j <= A; j++) { |
28 | dp[i + 1][j] = (dp[i + 1][j] + 1ll * dp[i][j] * (a[i].se - k - n - (A - j))) % mod; |
29 | } |
30 | A++; |
31 | } else { |
32 | for (int j = 0; j <= A; j++) { |
33 | dp[i + 1][j] = (dp[i + 1][j] + 1ll * dp[i][j] * (a[i].fi - j - C)) % mod; |
34 | } |
35 | C++; |
36 | } |
37 | } |
38 | return dp[n * 2][k]; |
39 | } |
40 | |
41 | int main() { |
42 | scanf("%d %d", &n, &mod); |
43 | for (int i = 0; i < n; i++) { |
44 | a[i] = make_pair(get(i * i, n * n - 1) + 1, get(i * i, n * n * 4) + 1); |
45 | } |
46 | for (int i = n; i < n * 2; i++) { |
47 | a[i] = make_pair(get(i * i, n * n * 4) + 1, -1); |
48 | } |
49 | sort(a, a + n * 2); |
50 | int ans = 0; |
51 | for (int i = 0; i <= n; i++) { |
52 | ans = (ans + 1ll * (i & 1 ? mod - 1 : 1) * solve(i)) % mod; |
53 | } |
54 | printf("%d\n", ans); |
55 | return 0; |
56 | } |
HamiltonianPaths
「Topcoder 14250」HamiltonianPaths
题目描述
给定一个 $n$ 个点的图,把它复制 $k$ 边,然后取补图,问新图的哈密尔顿路径条数,对 $998244353$ 取模。
数据范围:$n \le 14, k \le 5 \times 10^4$。
思路分析
也就是说限制整个完全图中不能经过 $km$ 条边。考虑容斥,枚举必须经过 $c$ 条边,那么对答案的贡献就是 $(-1)^c$ 乘上方案数。假设一个子图里经过了 $d$ 条不合法边,组成了 $e$ 条有向的链,那么将链定向后,对答案的贡献就是 $(-1)^d$,也就是说权值为 $(-1)^d$。最后如果缩点后还剩 $f$ 个点,方案数就是 $f!$。所以要算出每个图缩成 $g$ 个点的权值和,然后用 fft 求出大图缩成 $f$ 个点的权值和。一个子图的权值和可以通过简单状压 dp 实现,总时间复杂度 $O(2^n n^2 + 3^n n + kn \log kn)$。
代码实现
1 |
|
2 | using namespace std; |
3 | |
4 | const int maxn = 14, maxk = 1 << maxn, maxm = 1 << 20, mod = 998244353; |
5 | |
6 | class HamiltonianPaths { |
7 | public: |
8 | bool a[maxn + 3][maxn + 3]; |
9 | int n, k, dp[maxk + 3][maxn + 3], cnt[maxk + 3], f[maxk + 3][maxn + 3]; |
10 | int g[maxm + 3], h[maxm + 3], g_n, h_n, lim, bit, rev[maxm + 3], A[maxm + 3], B[maxm + 3]; |
11 | |
12 | inline int func(const int &x) { |
13 | return x < mod ? x : x - mod; |
14 | } |
15 | |
16 | int qpow(int a, int b) { |
17 | b < 0 ? b += mod - 1 : 0; |
18 | int c = 1; |
19 | for (; b; b >>= 1, a = 1ll * a * a % mod) { |
20 | if (b & 1) c = 1ll * a * c % mod; |
21 | } |
22 | return c; |
23 | } |
24 | |
25 | void dft(int a[], int n, int type) { |
26 | for (int i = 0; i < n; i++) { |
27 | if (i < rev[i]) swap(a[i], a[rev[i]]); |
28 | } |
29 | for (int k = 1; k < n; k <<= 1) { |
30 | int x = qpow(3, (mod - 1) / (k << 1) * type); |
31 | for (int i = 0; i < n; i += k << 1) { |
32 | int y = 1; |
33 | for (int j = i; j < i + k; j++, y = 1ll * x * y % mod) { |
34 | int p = a[j], q = 1ll * a[j + k] * y % mod; |
35 | a[j] = func(p + q), a[j + k] = func(p - q + mod); |
36 | } |
37 | } |
38 | } |
39 | if (type == -1) { |
40 | int x = qpow(n, mod - 2); |
41 | for (int i = 0; i < n; i++) { |
42 | a[i] = 1ll * a[i] * x % mod; |
43 | } |
44 | } |
45 | } |
46 | |
47 | void mult(int a[], int &n, int b[], int m) { |
48 | for (lim = 1, bit = 0; lim <= n + m; lim <<= 1) bit++; |
49 | for (int i = 1; i < lim; i++) { |
50 | rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (bit - 1)); |
51 | } |
52 | for (int i = 0; i <= n; i++) { |
53 | A[i] = a[i]; |
54 | } |
55 | for (int i = 0; i <= m; i++) { |
56 | B[i] = b[i]; |
57 | } |
58 | fill(A + n + 1, A + lim, 0); |
59 | fill(B + m + 1, B + lim, 0); |
60 | dft(A, lim, 1), dft(B, lim, 1); |
61 | for (int i = 0; i < lim; i++) { |
62 | A[i] = 1ll * A[i] * B[i] % mod; |
63 | } |
64 | dft(A, lim, -1); |
65 | n += m; |
66 | for (int i = 0; i <= n; i++) { |
67 | a[i] = A[i]; |
68 | } |
69 | } |
70 | |
71 | int countPaths(int N, vector<int> A, vector<int> B, int K) { |
72 | n = N, k = K; |
73 | for (int i = 0; i < A.size(); i++) { |
74 | a[A[i] + 1][B[i] + 1] = true; |
75 | a[B[i] + 1][A[i] + 1] = true; |
76 | } |
77 | for (int i = 1; i <= n; i++) { |
78 | dp[1 << (i - 1)][i] = 1; |
79 | } |
80 | for (int msk = 1; msk < 1 << n; msk++) { |
81 | for (int i = 1; i <= n; i++) if (dp[msk][i]) { |
82 | for (int j = 1; j <= n; j++) if (a[i][j] && (~msk >> (j - 1) & 1)) { |
83 | dp[msk | (1 << (j - 1))][j] = func(dp[msk | (1 << (j - 1))][j] - dp[msk][i] + mod); |
84 | } |
85 | } |
86 | for (int i = 1; i <= n; i++) if (dp[msk][i]) { |
87 | cnt[msk] = func(cnt[msk] + dp[msk][i]); |
88 | } |
89 | } |
90 | f[0][0] = 1; |
91 | for (int msk = 1; msk < 1 << n; msk++) { |
92 | int p = 0; |
93 | for (int i = 1; i <= n; i++) { |
94 | if (msk >> (i - 1) & 1) { |
95 | p = i; |
96 | break; |
97 | } |
98 | } |
99 | int t = msk ^ (1 << (p - 1)); |
100 | for (int k = 0; k < n; k++) { |
101 | f[msk][k + 1] = f[t][k]; |
102 | } |
103 | for (int i = t; i; i = (i - 1) & t) { |
104 | for (int k = 0; k < n; k++) { |
105 | f[msk][k + 1] = (f[msk][k + 1] + 1ll * cnt[i | (1 << (p - 1))] * f[msk ^ (i | 1 << (p - 1))][k]) % mod; |
106 | } |
107 | } |
108 | } |
109 | for (int i = 1; i <= n; i++) { |
110 | g[i] = f[(1 << n) - 1][i]; |
111 | } |
112 | h[0] = 1; |
113 | int g_n = n, h_n = 0; |
114 | for (int i = k; i; i >>= 1, mult(g, g_n, g, g_n)) { |
115 | if (i & 1) mult(h, h_n, g, g_n); |
116 | } |
117 | int cur = 1, ret = 0; |
118 | for (int i = 1; i <= n * k; i++) { |
119 | cur = 1ll * cur * i % mod; |
120 | ret = (ret + 1ll * h[i] * cur) % mod; |
121 | } |
122 | return ret; |
123 | } |
124 | }; |
Fireflies
题目描述
给定 $n$ 个数 $p_1, p_2, \cdots, p_n$,令 $M = \lfloor \frac{\sum_{i = 1}^{n} (p_i + 1)}{2} \rfloor$,问有多少 $n$ 元组 $(x_1, x_2, \cdots, x_n)$ 满足:
- $1 \le x_i \le p_i$
- $\sum_{i = 1}^{n} x_i = M$
数据范围:$T \le 2000, n \le 32$,至多两组数据满足 $n > 24$。
思路分析
注意:此题中的 $\binom{n}{m}$ 定义为 $\frac{n^{\underline{m}}}{m!}$,所以 $n < 0$ 时的组合数也可以计算。
考虑容斥,$1 \le x_i \le p_i$ 的方案数为 $[x_i > 0] - [x_i > p_i]$。考虑将强制大于 $p_i$ 的数减去 $p_i$,然后使用插板法,问题就转化成了计算:
直接计算不行,考虑折半搜索。使用范德蒙德恒等式:
用到这题里就是:
那么我们左边、右边分别预处理组合数,然后 2 - pointers 合并即可。时间复杂度 $O(2^{\frac{n}{2}} n)$。
代码实现
1 |
|
2 | using namespace std; |
3 | |
4 | typedef long long ll; |
5 | const int maxn = 32, maxm = 1 << (maxn >> 1), mod = 1e9 + 7; |
6 | int T, n, A, B, k, l, p[maxn + 3], inv[maxn + 3], s[maxn + 3]; |
7 | ll m; |
8 | |
9 | struct thing { |
10 | ll fi; |
11 | int se[maxn + 3]; |
12 | friend bool operator < (const thing &a, const thing &b) { |
13 | return a.fi < b.fi; |
14 | } |
15 | } a[maxm + 3], b[maxm + 3]; |
16 | |
17 | inline int func(const int &x) { |
18 | return x < mod ? x : x - mod; |
19 | } |
20 | |
21 | void prework(int n) { |
22 | inv[1] = 1; |
23 | for (int i = 2; i <= n; i++) { |
24 | inv[i] = 1ll * (mod - mod / i) * inv[mod % i] % mod; |
25 | } |
26 | } |
27 | |
28 | int main() { |
29 | prework(maxn); |
30 | scanf("%d", &T); |
31 | while (T --> 0) { |
32 | scanf("%d", &n); |
33 | ll sum = 0; |
34 | for (int i = 1; i <= n; i++) { |
35 | scanf("%d", &p[i]); |
36 | sum += p[i] + 1; |
37 | } |
38 | m = (sum >> 1); |
39 | A = n >> 1, B = n - A; |
40 | k = l = 0; |
41 | int ans = 0; |
42 | for (int msk = 0; msk < 1 << A; msk++) { |
43 | int y = 1; |
44 | ll z = 0; |
45 | for (int i = 1; i <= A; i++) { |
46 | if (msk >> (i - 1) & 1) { |
47 | y = mod - y, z += p[i]; |
48 | } |
49 | } |
50 | a[++k].fi = m - 1 - z; |
51 | int x = func((m - 1 - z) % mod + mod); |
52 | a[k].se[0] = y; |
53 | for (int i = 1; i < n; i++) { |
54 | a[k].se[i] = 1ll * a[k].se[i - 1] * (x - i + 1 + mod) % mod * inv[i] % mod; |
55 | } |
56 | } |
57 | for (int msk = 0; msk < 1 << B; msk++) { |
58 | int y = 1; |
59 | ll z = 0; |
60 | for (int i = 1; i <= B; i++) { |
61 | if (msk >> (i - 1) & 1) { |
62 | y = mod - y, z += p[i + A]; |
63 | } |
64 | } |
65 | b[++l].fi = -z; |
66 | int x = func((-z) % mod + mod); |
67 | b[l].se[0] = y; |
68 | for (int i = 1; i < n; i++) { |
69 | b[l].se[i] = 1ll * b[l].se[i - 1] * (x - i + 1 + mod) % mod * inv[i] % mod; |
70 | } |
71 | } |
72 | sort(a + 1, a + k + 1); |
73 | sort(b + 1, b + l + 1); |
74 | int p = l; |
75 | for (int i = 0; i < n; i++) { |
76 | s[i] = 0; |
77 | } |
78 | for (int i = 1; i <= k; i++) { |
79 | while (p && a[i].fi + b[p].fi >= 0) { |
80 | for (int j = 0; j < n; j++) { |
81 | s[j] = func(s[j] + b[p].se[j]); |
82 | } |
83 | p--; |
84 | } |
85 | for (int j = 0; j < n; j++) { |
86 | ans = (ans + 1ll * a[i].se[j] * s[n - j - 1]) % mod; |
87 | } |
88 | } |
89 | printf("%d\n", ans); |
90 | } |
91 | return 0; |
92 | } |