官方题解:2025 ICPC Asia EC Online Round2 官方题解 - 知乎
https://pintia.cn/rankings/1964904761885741056
二分答案,复杂度O ( log S ) \mathcal{O}(\log S) O ( log S ) 。
check 的想法是,匀一匀总能匀出来(似乎是 Hall 定理保证的)。
对于 maskt t t ,必须满足能为t t t 提供题目的F F F (即满足s ∩ t s \cap t s ∩ t 非空的F s F_{s} F s )之和⩾ ∣ t ∣ × mid \geqslant |t|\times\text{mid} ⩾ ∣ t ∣ × mid 。比如对于 2,3 这两个人,要满足能为 2,3 提供题目的F F F 之和(即F 2 , F 3 , … , F 7 F_{2},F_{3},\dots,F_{7} F 2 , F 3 , … , F 7 )⩾ 2 × mid \geqslant 2\times\text{mid} ⩾ 2 × mid 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #include <bits/stdc++.h> using namespace std;signed main () { int t; cin >> t; while (t--) { int S; cin >> S; array<int , 8> F{}; for (int i = 1 ; i < 8 ; i++) { cin >> F[i]; } auto check = [&](int mid) { for (unsigned t = 1 ; t < 8 ; t++) { int sum = 0 ; for (unsigned s = 1 ; s < 8 ; s++) { if ((s & t) > 0 ) { sum += F[s]; } } if (mid * popcount (t) > sum) { return false ; } } return true ; }; int lo = 0 , hi = S; while (lo <= hi) { int mid = lo + hi >> 1 ; if (!check (mid)) { hi = mid - 1 ; } else { lo = mid + 1 ; } } cout << lo - 1 << endl; } return 0 ; }
实际上这个二分可以省略。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <bits/stdc++.h> using namespace std;signed main () { int t; cin >> t; while (t--) { int S; cin >> S; array<int , 8> F{}; for (int i = 1 ; i < 8 ; i++) { cin >> F[i]; } int res = S; for (unsigned t = 1 ; t < 8 ; t++) { int sum = 0 ; for (unsigned s = 1 ; s < 8 ; s++) { if ((s & t) > 0 ) { sum += F[s]; } } res = min (res, sum / popcount (t)); } cout << res << endl; } return 0 ; }
只保留一项是最优的,而且是从大到小依次删除。
不妨升序排序A A A ,若序列第i i i 项作为子序列的第k k k 小,则这一位置的贡献系数是
f k = { 1 , k = 1 2 k − 2 , k ⩾ 2 f_{k} = \begin{cases} 1, & k=1 \\ 2^{k-2}, & k \geqslant 2 \end{cases} f k = { 1 , 2 k − 2 , k = 1 k ⩾ 2
前i − 1 i-1 i − 1 个数要选k − 1 k-1 k − 1 个,后面N − i N-i N − i 个数可选可不选,方案数是( i − 1 k − 1 ) 2 N − i \dbinom{i-1}{k-1} 2^{N-i} ( k − 1 i − 1 ) 2 N − i ,因此答案为
Answer = ∑ i = 1 N ∑ k = 1 i ( i − 1 k − 1 ) 2 N − i A i f k = ∑ i = 1 N 2 N − i A i [ 1 + ∑ k = 2 i ( i − 1 k − 1 ) 2 k − 2 ] = ∑ i = 1 N 2 N − i A i [ 1 + 1 2 ( 3 i − 1 − 1 ) ] \begin{aligned} \text{Answer} &= \sum_{i=1}^{N} \sum_{k=1}^{i} \binom{i-1}{k-1} 2^{N-i} A_{i} f_{k} \\ &= \sum_{i=1}^{N} 2^{N-i} A_{i} \bigg[1 + \sum_{k=2}^{i} \binom{i-1}{k-1} 2^{k-2} \bigg] \\ &= \sum_{i=1}^{N} 2^{N-i} A_{i} \bigg[1 + \dfrac{1}{2}(3^{i - 1} - 1) \bigg] \end{aligned} Answer = i = 1 ∑ N k = 1 ∑ i ( k − 1 i − 1 ) 2 N − i A i f k = i = 1 ∑ N 2 N − i A i [ 1 + k = 2 ∑ i ( k − 1 i − 1 ) 2 k − 2 ] = i = 1 ∑ N 2 N − i A i [ 1 + 2 1 ( 3 i − 1 − 1 ) ]
复杂度O ( N ) \mathcal{O}(N) O ( N ) 。
(写这个题的时候还没有写取模类)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <bits/stdc++.h> using namespace std;constexpr int mod = 998'244'353 ;constexpr int inv2 = mod + 1 >> 1 ;signed main () { cin.tie (nullptr )->sync_with_stdio (false ); int t; cin >> t; while (t--) { int n; cin >> n; vector<int > a (n) ; for (auto & x : a) cin >> x; sort (a.begin (), a.end ()); vector<int > pow2 (n + 1 ) ; pow2[0 ] = 1 ; for (int i = 0 ; i < n; i++) { pow2[i + 1 ] = 2LL * pow2[i] % mod; } int res = 0 ; int pow3 = 1 ; for (int i = 0 ; i < n; i++) { res += 1LL * a[i] * ((1 + 1LL * (pow3 - 1 ) * inv2 % mod) % mod) % mod * pow2[n - 1 - i] % mod; res %= mod; pow3 = 1LL * pow3 * 3 % mod; } if (res < 0 ) { res += mod; } cout << res << endl; } return 0 ; }
方法 1 构建递推数列设f n f_{n} f n 表示长n n n 序列,值域为M M M 的答案,则
f n = 2 M ( 2 M − 1 ) n − 2 − f n − 2 ( 2 M − 1 ) f_{n} = 2^{M} \big(2^{M}-1 \big)^{n-2} - f_{n-2} \big(2^{M}-1 \big) f n = 2 M ( 2 M − 1 ) n − 2 − f n − 2 ( 2 M − 1 )
含义是,第一个数有2 M 2^{M} 2 M 种选法,第2 ∼ n − 1 2\sim n-1 2 ∼ n − 1 个数皆有2 M − 1 2^{M}-1 2 M − 1 种选法,最后一个数是唯一确定的,因为要保证异或和为 0。但是这样可能会导致最后两个数相同,此时前n − 2 n-2 n − 2 个数的异或和为 0,是合法序列,方案数有f n − 2 f_{n-2} f n − 2 ,需要减去f n − 2 ( 2 M − 1 ) f_{n-2} \big(2^{M}-1 \big) f n − 2 ( 2 M − 1 ) 。
方法 1.1 直接求解递推数列f n = 2 M ( 2 M − 1 ) n − 2 − f n − 2 ( 2 M − 1 ) f_{n} = 2^{M} \big(2^{M}-1 \big)^{n-2} - f_{n-2} \big(2^{M}-1 \big) f n = 2 M ( 2 M − 1 ) n − 2 − f n − 2 ( 2 M − 1 )
递推式等价于
[ f n − ( 2 M − 1 ) n − 1 ] + ( 2 M − 1 ) [ f n − 2 − ( 2 M − 1 ) n − 3 ] = 0 \Big[f_{n} - \big(2^{M}-1 \big)^{n-1} \Big] + \big(2^{M}-1 \big) \Big[f_{n-2} - \big(2^{M}-1 \big)^{n-3} \Big] = 0 [ f n − ( 2 M − 1 ) n − 1 ] + ( 2 M − 1 ) [ f n − 2 − ( 2 M − 1 ) n − 3 ] = 0
利用累乘法,得到通项
f N = { ( 2 M − 1 ) N − 1 + ( 1 − 2 M ) N / 2 − 1 ( f 2 − ( 2 M − 1 ) ) , 2 ∣ N ( 2 M − 1 ) N − 1 + ( 1 − 2 M ) ( N + 1 ) / 2 − 1 ( f 1 − 1 ) , 2 ∤ N f_{N} = \begin{cases} \big(2^{M}-1 \big)^{N-1} + \big(1 - 2^{M} \big)^{N/2-1} \big(f_{2} - \big(2^{M}-1 \big) \big), & 2 \mid N \\ \big(2^{M}-1 \big)^{N-1} + \big(1 - 2^{M} \big)^{(N+1)/2-1} (f_{1} - 1 ), & 2 \nmid N \end{cases} f N = { ( 2 M − 1 ) N − 1 + ( 1 − 2 M ) N / 2 − 1 ( f 2 − ( 2 M − 1 ) ) , ( 2 M − 1 ) N − 1 + ( 1 − 2 M ) ( N + 1 ) / 2 − 1 ( f 1 − 1 ) , 2 ∣ N 2 ∤ N
代入初始条件
{ f 1 = 1 f 2 = 0 \begin{cases} f_{1} = 1 \\ f_{2} = 0 \end{cases} { f 1 = 1 f 2 = 0
就得到了最终结果
f N = { ( 2 M − 1 ) N − 1 + ( 1 − 2 M ) N / 2 , 2 ∣ N ( 2 M − 1 ) N − 1 , 2 ∤ N f_{N} = \begin{cases} \big(2^{M}-1 \big)^{N-1} + \big(1 - 2^{M} \big)^{N/2}, & 2 \mid N \\ \big(2^{M}-1 \big)^{N-1}, & 2 \nmid N \end{cases} f N = { ( 2 M − 1 ) N − 1 + ( 1 − 2 M ) N / 2 , ( 2 M − 1 ) N − 1 , 2 ∣ N 2 ∤ N
复杂度O ( log M + log N ) \mathcal{O}(\log M + \log N) O ( log M + log N ) 。
代码中 Z 是取模类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 signed main () { int t; cin >> t; while (t--) { int N, M; cin >> N >> M; Z g = qpow (Z (2 ), M) - 1 ; Z res = qpow (g, N - 1 ); if (N % 2 == 0 ) { res += qpow (-g, N / 2 ); } cout << res << endl; } return 0 ; }
方法 1.2 矩阵快速幂f n = 2 M ( 2 M − 1 ) n − 2 − f n − 2 ( 2 M − 1 ) f_{n} = 2^{M} \big(2^{M}-1 \big)^{n-2} - f_{n-2} \big(2^{M}-1 \big) f n = 2 M ( 2 M − 1 ) n − 2 − f n − 2 ( 2 M − 1 )
这个东西是线性递推(线性变换),可以用矩阵表示,但不是齐次常系数。为写成矩阵的形式,需要在矩阵里添加一维存储( 2 M − 1 ) n \big(2^{M}-1 \big)^{n} ( 2 M − 1 ) n :
[ f n f n − 2 ( 2 M − 1 ) n ] = [ − ( 2 M − 1 ) 0 2 M 1 0 0 0 0 ( 2 M − 1 ) 2 ] [ f n − 2 f n − 4 ( 2 M − 1 ) n − 2 ] \begin{bmatrix} f_{n} \\ f_{n-2} \\ \big(2^{M}-1 \big)^{n} \end{bmatrix} = \begin{bmatrix} -\big(2^{M}-1 \big) & 0 & 2^{M} \\ 1 & 0 & 0 \\ 0 & 0 & \big(2^{M}-1 \big)^{2} \end{bmatrix} \begin{bmatrix} f_{n-2} \\ f_{n-4} \\ \big(2^{M}-1 \big)^{n-2} \end{bmatrix} ⎣ ⎢ ⎡ f n f n − 2 ( 2 M − 1 ) n ⎦ ⎥ ⎤ = ⎣ ⎢ ⎡ − ( 2 M − 1 ) 1 0 0 0 0 2 M 0 ( 2 M − 1 ) 2 ⎦ ⎥ ⎤ ⎣ ⎢ ⎡ f n − 2 f n − 4 ( 2 M − 1 ) n − 2 ⎦ ⎥ ⎤
然后分奇偶快速幂就好了。
复杂度O [ K 3 ( log M + log N ) ] \mathcal{O}[K^{3}(\log M + \log N)] O [ K 3 ( log M + log N ) ] ,K = 3 K=3 K = 3 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 struct Matrix : array<array<Z, 3>, 3> { Matrix (int x = 0 ) { for (int i = 0 ; i < 3 ; i++) { (*this )[i][i] = x; } } friend Matrix operator * (const Matrix& a, const Matrix& b) { Matrix res; for (int i = 0 ; i < 3 ; i++) { for (int j = 0 ; j < 3 ; j++) { for (int k = 0 ; k < 3 ; k++) { res[i][j] += a[i][k] * b[k][j]; } } } return res; } }; int main () { int t; cin >> t; while (t--) { ll N, M; cin >> N >> M; if (N == 1 ) { cout << 1 << endl; continue ; } Z g = qpow (Z (2 ), M) - 1 ; Matrix T; T[0 ][0 ] = -g; T[0 ][2 ] = g + 1 ; T[1 ][0 ] = 1 ; T[2 ][2 ] = g * g; Matrix v0; ll exp; if (N % 2 == 0 ) { v0[0 ][0 ] = 0 ; v0[1 ][0 ] = 0 ; v0[2 ][0 ] = g * g; exp = (N - 2 ) / 2 ; } else { v0[0 ][0 ] = g * g; v0[1 ][0 ] = 1 ; v0[2 ][0 ] = g * g * g; exp = (N - 3 ) / 2 ; } Matrix v = qpow (T, exp, Matrix (1 )) * v0; cout << v[0 ][0 ] << endl; } return 0 ; }
方法 2 对差分计数(官方题解的方法)对于奇数长度,只需要确定后N − 1 N-1 N − 1 项与前一项的异或值d i d_{i} d i ,第一项的值就自然确定,这是因为
A 1 ⊕ ( A 1 ⊕ d 1 ) ⊕ ( A 1 ⊕ d 1 ⊕ d 2 ) ⊕ ⋯ = 0 ⟹ A 1 = d 2 ⊕ d 4 ⊕ … A_{1} \oplus (A_{1} \oplus d_{1}) \oplus (A_{1} \oplus d_{1} \oplus d_{2}) \oplus \dots=0 \Longrightarrow A_{1} = d_{2} \oplus d_{4} \oplus \dots A 1 ⊕ ( A 1 ⊕ d 1 ) ⊕ ( A 1 ⊕ d 1 ⊕ d 2 ) ⊕ ⋯ = 0 ⟹ A 1 = d 2 ⊕ d 4 ⊕ …
由于每个d i d_{i} d i 非零,选d i d_{i} d i 的方法数是( 2 M − 1 ) \big(2^{M}-1 \big) ( 2 M − 1 ) ,总方法数( 2 M − 1 ) N − 1 \big(2^{M}-1 \big)^{N-1} ( 2 M − 1 ) N − 1 。
对于偶数长度,也有类似的式子
A 1 ⊕ ( A 1 ⊕ d 1 ) ⊕ ( A 1 ⊕ d 1 ⊕ d 2 ) ⊕ ⋯ = 0 A_{1} \oplus (A_{1} \oplus d_{1}) \oplus (A_{1} \oplus d_{1} \oplus d_{2}) \oplus \dots=0 A 1 ⊕ ( A 1 ⊕ d 1 ) ⊕ ( A 1 ⊕ d 1 ⊕ d 2 ) ⊕ ⋯ = 0
化简出来等价于
0 = d 1 ⊕ d 3 ⊕ … 0 = d_{1} \oplus d_{3} \oplus \dots 0 = d 1 ⊕ d 3 ⊕ …
另外一个条件是每个d i d_{i} d i 非零。现在求解d 1 , d 3 . , , , d_{1},d_{3}.,,, d 1 , d 3 . , , , 的方案数。
记f ( n ) f(n) f ( n ) 表示长度为n n n 的序列,满足异或和为 0 且每一项非零的方案数;记g ( n ) g(n) g ( n ) 表示长度为n n n 的序列,满足异或和为 0 的方案数。
考虑用f f f 表示g g g ,从长度为n n n 的序列中选出n − m n-m n − m 项为 0,剩余m m m 项非零,就有
g ( n ) = ∑ m = 0 n ( n m ) f ( m ) \displaystyle g(n) = \sum_{m = 0}^{n} \dbinom{n}{m} f(m) g ( n ) = m = 0 ∑ n ( m n ) f ( m )
做二项式反演得到
f ( n ) = ∑ m = 0 n ( − 1 ) n − m ( n m ) g ( m ) \displaystyle f(n) = \sum_{m = 0}^{n}(-1)^{n - m} \dbinom{n}{m} g(m) f ( n ) = m = 0 ∑ n ( − 1 ) n − m ( m n ) g ( m )
根据定义g ( n ) = 2 M ( n − 1 ) g(n) = 2^{M(n-1)} g ( n ) = 2 M ( n − 1 ) (并补充定义g ( 0 ) = 1 g(0)=1 g ( 0 ) = 1 )代入得到
f ( n ) = ( − 1 ) n − 0 ( n 0 ) g ( 0 ) + ∑ m = 1 n ( − 1 ) n − m ( n m ) g ( m ) = ( − 1 ) n + ∑ m = 1 n ( − 1 ) n − m ( n m ) 2 M ( m − 1 ) = ( − 1 ) n − ( − 1 ) n − 0 ( n 0 ) 2 M ( 0 − 1 ) + ∑ m = 0 n ( − 1 ) n − m ( n m ) 2 M ( m − 1 ) = ( − 1 ) n − ( − 1 ) n 2 − M + 2 − M ∑ m = 0 n ( − 1 ) n − m ( n m ) 2 M m = ( − 1 ) n − ( − 1 ) n 2 − M + 2 − M ( 2 M − 1 ) n \begin{aligned} f(n) &= (-1)^{n - 0} \dbinom{n}{0} g(0) + \sum_{m = 1}^{n}(-1)^{n - m} \dbinom{n}{m} g(m) \\ &= (-1)^{n} + \sum_{m = 1}^{n}(-1)^{n - m} \dbinom{n}{m} 2^{M (m-1)} \\ &= (-1)^{n} - (-1)^{n - 0} \dbinom{n}{0} 2^{M (0-1)} + \sum_{m = 0}^{n}(-1)^{n - m} \dbinom{n}{m} 2^{M (m-1)} \\ &= (-1)^{n} - (-1)^{n} 2^{-M} + 2^{-M} \sum_{m = 0}^{n}(-1)^{n - m} \dbinom{n}{m} 2^{M m} \\ &= (-1)^{n} - (-1)^{n} 2^{-M} + 2^{-M} \big(2^{M} - 1 \big)^{n} \\ \end{aligned} f ( n ) = ( − 1 ) n − 0 ( 0 n ) g ( 0 ) + m = 1 ∑ n ( − 1 ) n − m ( m n ) g ( m ) = ( − 1 ) n + m = 1 ∑ n ( − 1 ) n − m ( m n ) 2 M ( m − 1 ) = ( − 1 ) n − ( − 1 ) n − 0 ( 0 n ) 2 M ( 0 − 1 ) + m = 0 ∑ n ( − 1 ) n − m ( m n ) 2 M ( m − 1 ) = ( − 1 ) n − ( − 1 ) n 2 − M + 2 − M m = 0 ∑ n ( − 1 ) n − m ( m n ) 2 M m = ( − 1 ) n − ( − 1 ) n 2 − M + 2 − M ( 2 M − 1 ) n
此外,A 1 A_{1} A 1 有2 M 2^{M} 2 M 种选法,d 2 , d 4 d_{2},d_{4} d 2 , d 4 任选非零的数,有( 2 M − 1 ) N / 2 − 1 \big(2^{M}-1 \big)^{N/2-1} ( 2 M − 1 ) N / 2 − 1 种选法,总的方案数是
Answer = 2 M ( 2 M − 1 ) N / 2 − 1 f ( N 2 ) = ( 2 M − 1 ) N / 2 − 1 [ 2 M ( − 1 ) N / 2 − ( − 1 ) N / 2 + ( 2 M − 1 ) N / 2 ] = ( 2 M − 1 ) N / 2 − 1 [ ( 2 M − 1 ) ( − 1 ) N / 2 + ( 2 M − 1 ) N / 2 ] = ( 2 M − 1 ) N − 1 + ( 1 − 2 M ) N / 2 \begin{aligned} \text{Answer} &= 2^{M} \big(2^{M}-1 \big)^{N/2-1} f\bigg(\dfrac{N}{2}\bigg) \\ &= \big(2^{M}-1 \big)^{N/2-1} \Big[2^{M} (-1)^{N/2} - (-1)^{N/2} + \big(2^{M} - 1 \big)^{N/2} \Big] \\ &= \big(2^{M}-1 \big)^{N/2-1} \Big[\big( 2^{M} - 1 \big) (-1)^{N/2} + \big(2^{M} - 1 \big)^{N/2} \Big] \\ &= \big(2^{M}-1 \big)^{N-1} + \big(1 - 2^{M} \big)^{N/2} \end{aligned} Answer = 2 M ( 2 M − 1 ) N / 2 − 1 f ( 2 N ) = ( 2 M − 1 ) N / 2 − 1 [ 2 M ( − 1 ) N / 2 − ( − 1 ) N / 2 + ( 2 M − 1 ) N / 2 ] = ( 2 M − 1 ) N / 2 − 1 [ ( 2 M − 1 ) ( − 1 ) N / 2 + ( 2 M − 1 ) N / 2 ] = ( 2 M − 1 ) N − 1 + ( 1 − 2 M ) N / 2
枚举端点x , y x,y x , y 并钦定这两个端点满足a x ≠ x , a y ≠ y a_{x} \neq x,a_{y} \neq y a x = x , a y = y ,中间的点随意。设链长是n n n ,容斥得到方案数是
f ( n ) = n ! − 2 ( n − 1 ) ! + ( n − 2 ) ! f(n) = n!-2(n-1)!+(n-2)! f ( n ) = n ! − 2 ( n − 1 ) ! + ( n − 2 ) !
表示这n n n 个点都任意 shuffle 的方案数,减去a x = x a_{x} = x a x = x 的方案数,再减去a y = y a_{y} = y a y = y 的方案数,再加上同时满足a x = x a_{x} = x a x = x 和a y = y a_{y} = y a y = y 的方案数。
复杂度O ( N 2 ) \mathcal{O}(N^{2}) O ( N 2 ) 。
代码中 Z 是取模类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 signed main () { int t; cin >> t; while (t--) { int N; cin >> N; vector<vector<int >> adj (N); for (int i = 1 ; i < N; i++) { int x, y; cin >> x >> y; x--; y--; adj[x].push_back (y); adj[y].push_back (x); } vector<int > d (N) ; vector<vector<int >> subtree (N); auto dfs = [&](auto && self, int x, int p) -> void { if (p != -1 ) { adj[x].erase (find (adj[x].begin (), adj[x].end (), p)); } subtree[x].push_back (x); for (auto y : adj[x]) { d[y] = d[x] + 1 ; self (self, y, x); for (auto z : subtree[y]) { subtree[x].push_back (z); } } }; dfs (dfs, 0 , -1 ); auto cal = [&](int n) -> Z { assert (n != 1 ); return fac[n] - 2 * fac[n - 1 ] + fac[n - 2 ]; }; Z res = 0 ; for (int lca = 0 ; lca < N; lca++) { for (int i = 0 ; i < adj[lca].size (); i++) { for (auto x : subtree[adj[lca][i]]) { int n = d[x] - d[lca] + 1 ; res += cal (n); for (int j = 0 ; j < i; j++) { for (auto y : subtree[adj[lca][j]]) { int n = d[x] + d[y] - 2 * d[lca] + 1 ; res += cal (n); } } } } } res += 1 ; cout << res << endl; } return 0 ; }
路径上至多N − 1 N-1 N − 1 条边,因此答案是关于k k k 的N − 1 N-1 N − 1 次多项式。
询问f ( 1 , N , i ) f(1,N,i) f ( 1 , N , i ) ,i = 1 , 2 , … , N − 1 i=1,2,\dots,N-1 i = 1 , 2 , … , N − 1 ,又已知f ( 1 , N , 0 ) = 0 f(1,N,0)=0 f ( 1 , N , 0 ) = 0 ,做 Lagrange 插值:
f ( 1 , N , K ) = ∑ k = 0 N − 1 f ( 1 , N , k ) ∏ i : i ≠ k K − i k − i \displaystyle f(1,N,K) = \sum_{k=0}^{N-1} f(1,N,k) \prod_{i: i \neq k} \dfrac{K-i}{k-i} f ( 1 , N , K ) = k = 0 ∑ N − 1 f ( 1 , N , k ) i : i = k ∏ k − i K − i
复杂度O ( N ) \mathcal{O}(N) O ( N ) 。
代码中 Z 是取模类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 signed main () { int N, M; cin >> N >> M; for (int i = 0 ; i < M; i++) { int x, y; cin >> x >> y; } vector<int > f (N) ; for (int i = 1 ; i < N; i++) { cout << "? 1 " << N << " " << i << endl; cin >> f[i]; } cout << "!" << endl; int K; cin >> K; N--; vector<Z> pre (N + 1 ) , suf (N + 1 ) ; pre[0 ] = 1 ; for (int i = 0 ; i < N; i++) { pre[i + 1 ] = pre[i] * (K - i); } suf[N] = 1 ; for (int i = N; i > 0 ; i--) { suf[i - 1 ] = suf[i] * (K - i); } Z res = 0 ; for (int k = 0 ; k <= N; k++) { res += f[k] * pre[k] * suf[k] * invfac[k] * invfac[N - k] * (N - k & 1 ? -1 : 1 ); } cout << res << endl; return 0 ; }
为所给的无序对u − v u-v u − v 连边,必然构成完全k k k 分图(能划分成若干组,同一组内任意两点无边,不同组内任意两点有边)。
设有m m m 个点未出现在输入中(代码中的 other),分别标号1 ′ , 2 ′ , … 1',2',\dots 1 ′ , 2 ′ , … ,那么把组i i i 中的点挂在标号i ′ i' i ′ 上,再把这些标号i ′ i' i ′ 挂在另一个未出现的点上,就构造出了一种方案。于是有解时必须k < m k<m k < m 。
判断完全k k k 分图:u , v u,v u , v 在同一集合中 ⟺ \iff ⟺ u , v u,v u , v 的邻接表相同。先将邻接表相同的点放入同一组,再检查是否满足不同组内任意两点有边。
判断集合相同:XOR Hash。
此外,完全图、链及完全二分图的情况略有不同,不过大体思路都是一样的。需要大力分类讨论。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 #include <bits/stdc++.h> using namespace std;using ull = unsigned long long ;mt19937_64 rnd (chrono::steady_clock::now().time_since_epoch().count()) ;signed main () { cin.tie (nullptr )->sync_with_stdio (false ); int t; cin >> t; while (t--) { int N, M; cin >> N >> M; vector<vector<int >> adj (N); set<int > pos; set<int > other; for (int x = 0 ; x < N; x++) { other.insert (x); } for (int _ = 0 ; _ < M; _++) { int x, y; cin >> x >> y; x--; y--; adj[x].push_back (y); adj[y].push_back (x); pos.insert (x); pos.insert (y); other.erase (x); other.erase (y); } map<ull, vector<int >> mp; vector<ull> hash (N) ; vector<ull> h (N) ; for (int x = 0 ; x < N; x++) { h[x] = rnd (); } for (auto p : pos) { for (auto x : adj[p]) { hash[p] ^= h[x]; } mp[hash[p]].push_back (p); } bool ok = true ; for (auto p : pos) { if (mp[hash[p]].size () + adj[p].size () != pos.size ()) { ok = false ; } } int k = mp.size (); if (!ok) { cout << "NO" << endl; continue ; } if (k == pos.size ()) { if (k == 2 ) { cout << "YES" << endl; if (N == 2 ) { cout << "1 2" << endl; } else { auto st = *other.begin (); other.erase (other.begin ()); auto ed = st; while (other.size ()) { auto now = *other.begin (); other.erase (other.begin ()); cout << now + 1 << " " << ed + 1 << endl; ed = now; } cout << *pos.begin () + 1 << " " << st + 1 << endl; cout << *prev (pos.end ()) + 1 << " " << ed + 1 << endl; } } else if (other.size () == 1 ) { cout << "YES" << endl; auto st = *other.begin (); for (auto ed : pos) { cout << ed + 1 << " " << st + 1 << endl; } } else if (k < other.size ()) { cout << "YES" << endl; auto st = *other.begin (); other.erase (other.begin ()); while (pos.size ()) { auto ed = *pos.begin (); auto mid = *other.begin (); other.erase (other.begin ()); pos.erase (pos.begin ()); cout << ed + 1 << " " << mid + 1 << endl; cout << mid + 1 << " " << st + 1 << endl; } while (other.size ()) { auto mid = *other.begin (); other.erase (other.begin ()); cout << mid + 1 << " " << st + 1 << endl; } } else { cout << "NO" << endl; } } else { if (k == 2 ) { if (other.size () < 2 ) { cout << "NO" << endl; } else { cout << "YES" << endl; auto st = *other.begin (); other.erase (other.begin ()); auto ed = st; while (other.size ()) { auto now = *other.begin (); other.erase (other.begin ()); cout << now + 1 << " " << ed + 1 << endl; ed = now; } for (auto p : (*mp.begin ()).second) { cout << p + 1 << " " << st + 1 << endl; } for (auto p : (*mp.rbegin ()).second) { cout << p + 1 << " " << ed + 1 << endl; } } } else { if (other.size () <= k) { cout << "NO" << endl; } else { cout << "YES" << endl; auto st = *other.begin (); other.erase (other.begin ()); for (auto & [_, vec] : mp) { auto mid = *other.begin (); other.erase (other.begin ()); for (auto p : vec) { cout << p + 1 << " " << mid + 1 << endl; } cout << mid + 1 << " " << st + 1 << endl; } while (!other.empty ()) { auto mid = *other.begin (); other.erase (other.begin ()); cout << mid + 1 << " " << st + 1 << endl; } } } } } return 0 ; }
题目说的就是重心。重心有两个 ⟺ \iff ⟺ 存在一条边,它两边的点的数量相等。
我们计算重心有两个的方案数,考虑 DP。
状态表示 :
f u , k f_{u,k} f u , k 表示以u u u 为根的子树内,包含节点u u u 的连通块大小为k k k 的数量;g u , k g_{u,k} g u , k 表示包含u u u 的父节点,不含u u u 的子树的连通块大小为k k k 的数量。转移方程 :将f u , g u f_{u},g_{u} f u , g u 视为多项式,即设
f u ( z ) = ∑ k = 0 ∞ z k f u , k g u ( z ) = ∑ k = 0 ∞ z k g u , k \begin{aligned} f_{u}(z) = \sum_{k=0}^{\infty} z^{k} f_{u,k} \\ g_{u}(z) = \sum_{k=0}^{\infty} z^{k} g_{u,k} \end{aligned} f u ( z ) = k = 0 ∑ ∞ z k f u , k g u ( z ) = k = 0 ∑ ∞ z k g u , k
则
f u ( z ) = z × ∏ v ∈ children ( u ) ( f v + 1 ) g u ( z ) = z × ( g parent ( u ) + 1 ) × ∏ c ∈ sibling ( u ) ( f c + 1 ) \begin{aligned} f_{u}(z) &= z \times \prod_{v \in \text{children}(u)} (f_v + 1) \\ g_u(z) &= z \times (g_{\operatorname{parent}(u)} + 1) \times \prod_{c \in \text{sibling}(u)} (f_c+1) \end{aligned} f u ( z ) g u ( z ) = z × v ∈ children ( u ) ∏ ( f v + 1 ) = z × ( g p a r e n t ( u ) + 1 ) × c ∈ sibling ( u ) ∏ ( f c + 1 )
多项式乘法的含义是对连通块大小做背包,其中z z z 项表示必须选u u u 点,+ 1 +1 + 1 表示可以不选整个子树。
g u g_{u} g u 中c ∈ sibling ( u ) c \in \text{sibling}(u) c ∈ sibling ( u ) 部分用前后缀优化容易求得。由于g u g_{u} g u 只有k ⩽ size ( u ) k \leqslant \text{size}(u) k ⩽ size ( u ) 的部分有用,边做乘法边 resize,复杂度是经典树形背包的复杂度O ( N 2 ) \mathcal{O}(N^{2}) O ( N 2 ) 。
最终答案 :
∑ u = 1 N ∑ k = 1 N f u , k − f u , k g u , k \displaystyle \sum_{u=1}^{N} \sum_{k=1}^{N} f_{u,k} - f_{u,k} g_{u,k} u = 1 ∑ N k = 1 ∑ N f u , k − f u , k g u , k
代码1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 vector<Z> operator * (const vector<Z>& a, const vector<Z>& b) { vector<Z> c (a.size() + b.size() - 1 ) ; for (int i = 0 ; i < a.size (); i++) { for (int j = 0 ; j < b.size (); j++) { c[i + j] += a[i] * b[j]; } } return c; } signed main () { cin.tie (nullptr )->sync_with_stdio (false ); int t; cin >> t; while (t--) { int n; cin >> n; vector<vector<int >> adj (n); for (int i = 1 ; i < n; i++) { int x, y; cin >> x >> y; x--, y--; adj[x].push_back (y); adj[y].push_back (x); } vector<vector<Z>> f (n); vector<int > parent (n, -1 ) ; auto dfs = [&](auto && self, int x) -> void { if (parent[x] != -1 ) { adj[x].erase (find (adj[x].begin (), adj[x].end (), parent[x])); } f[x] = { 0 , 1 }; for (auto y : adj[x]) { if (y == parent[x]) continue ; parent[y] = x; self (self, y); f[x] = f[x] * f[y]; } f[x][0 ] = 1 ; }; dfs (dfs, 0 ); vector<vector<Z>> g (n); auto dfs2 = [&](auto && self, int x) -> void { int m = adj[x].size (); if (m == 0 ) { return ; } vector<vector<Z>> pre (m + 1 ), suf (m); pre[0 ] = { 1 }; for (int i = 0 ; i < m; i++) { pre[i + 1 ] = pre[i] * f[adj[x][i]]; } suf[m - 1 ] = { 1 }; for (int i = m - 1 ; i > 0 ; i--) { suf[i - 1 ] = suf[i] * f[adj[x][i]]; } for (int i = 0 ; i < m; i++) { auto y = adj[x][i]; g[y] = { 0 , 1 }; g[y] = g[y] * g[x]; g[y].resize (f[y].size ()); g[y] = g[y] * pre[i]; g[y].resize (f[y].size ()); g[y] = g[y] * suf[i]; g[y].resize (f[y].size ()); g[y][0 ] = 1 ; self (self, y); } }; g[0 ] = { 1 }; dfs2 (dfs2, 0 ); Z res = 0 ; for (int x = 0 ; x < n; x++) { res += accumulate (f[x].begin (), f[x].end (), Z (0 )); for (int k = 0 ; k < min (g[x].size (), f[x].size ()); k++) { res -= g[x][k] * f[x][k]; } } cout << res << endl; } return 0 ; }
复杂度分析f f f 的复杂度分析 (经典树形背包):
设u u u 是v v v 的父节点,对于f u ← f u × f v f_{u} \gets f_{u} \times f_v f u ← f u × f v ,f v f_v f v 的长度恰好是size ( v ) \operatorname{size}(v) s i z e ( v ) ,而转移之前f u f_{u} f u 的长度至多为size ( u ) − size ( v ) \operatorname{size}(u) - \operatorname{size}(v) s i z e ( u ) − s i z e ( v ) ,因此单次乘法复杂度size ( v ) ⋅ [ size ( u ) − size ( v ) ] \operatorname{size}(v) \cdot [\operatorname{size}(u) - \operatorname{size}(v)] s i z e ( v ) ⋅ [ s i z e ( u ) − s i z e ( v ) ] ,求和得到:
∑ u = 1 N ∑ v ∈ children ( u ) size ( v ) ⋅ [ size ( u ) − size ( v ) ] = ∑ u = 1 N [ size ( u ) ( ∑ v ∈ children ( u ) size ( v ) ) − ∑ v ∈ children ( u ) size 2 ( v ) ] = ∑ u = 1 N [ size 2 ( u ) − ∑ v ∈ children ( u ) size 2 ( v ) ] = size 2 ( root ) = N 2 \begin{aligned} &\ \ \ \, \ \sum_{u=1}^{N} \sum_{v \in \operatorname{children}(u)} \operatorname{size}(v) \cdot [\operatorname{size}(u) - \operatorname{size}(v)] \\ &= \sum_{u=1}^{N} \Bigg[\operatorname{size}(u) \Bigg(\sum_{v \in \operatorname{children}(u)} \operatorname{size}(v) \Bigg) - \sum_{v \in \operatorname{children}(u)} \operatorname{size}^{2}(v) \Bigg] \\ &= \sum_{u=1}^{N} \Bigg[\operatorname{size}^{2}(u) - \sum_{v \in \operatorname{children}(u)} \operatorname{size}^{2}(v) \Bigg] \\ &= \operatorname{size}^{2}(\text{root}) = N^{2} \end{aligned} u = 1 ∑ N v ∈ c h i l d r e n ( u ) ∑ s i z e ( v ) ⋅ [ s i z e ( u ) − s i z e ( v ) ] = u = 1 ∑ N [ s i z e ( u ) ( v ∈ c h i l d r e n ( u ) ∑ s i z e ( v ) ) − v ∈ c h i l d r e n ( u ) ∑ s i z e 2 ( v ) ] = u = 1 ∑ N [ s i z e 2 ( u ) − v ∈ c h i l d r e n ( u ) ∑ s i z e 2 ( v ) ] = s i z e 2 ( root ) = N 2
更直观的理解是,任意两个点只在它们的 LCA 处发生一次交互,产生 1 的时间复杂度贡献。
g g g 的复杂度分析 :
前后缀预处理部分与f f f 的形式类似,复杂度相同。
对于转移g v ← g v × pre v g_{v} \gets g_{v} \times \text{pre}_{v} g v ← g v × pre v 和g v ← g v × suf v g_{v} \gets g_{v} \times \text{suf}_{v} g v ← g v × suf v ,由于已经限定g v g_{v} g v 长度至多size ( v ) \operatorname{size}(v) s i z e ( v ) ,而且pre v \text{pre}_{v} pre v 的长度至多为size ( u ) − size ( v ) \operatorname{size}(u) - \operatorname{size}(v) s i z e ( u ) − s i z e ( v ) ,所以单次乘法也是size ( v ) ⋅ [ size ( u ) − size ( v ) ] \operatorname{size}(v) \cdot [\operatorname{size}(u) - \operatorname{size}(v)] s i z e ( v ) ⋅ [ s i z e ( u ) − s i z e ( v ) ] ,求和结果与上面相同。总复杂度O ( N 2 ) \mathcal{O}(N^{2}) O ( N 2 ) 。
另外,如果先算 pre * suf 复杂度就没有保证了,会 TLE。