*注意:这套题目应版权方要求,不得公示题面。
从这里开始
- Problem A Caster的矩阵
- Problem B Berserker的power
- Problem C Assassin的森林
T1又是我不擅长的构造题,好不容猜出结论,然后没开long long,发现还是10分以为是错的,sad。T3傻逼推错式子,30分dp都挂掉了。下午补了补多项式求exp,然后Joker给我科普了一个式子,然后就会做了,发现多项式求$\ln$的板子丢了,重新写,挂了边界问题调了大半个下午,sad。真是sad的一天。
Problem A Caster的矩阵
题目大意
构造一个$n\times n$的矩阵,使得$1, 2, 3, \dots, n^{2}$各出现1次,要求最小化每行和每列的最大值与最小值之差的最大值。
当$n\leqslant 600$时要求输出方案,否则只输出最小的值。$m$为偶数。
手玩$n = 2, 4, 6$的时候发现答案分别为$2, 9, 20$,猜想答案是关于边长的二次函数,然后得到:$ans = \frac{1}{2}n^{2} + \frac{1}{2}n - 1$。
我们来想一下,这个式子有着什么意义(瞎猜):$\frac{1}{2}n^{2}$是半个矩阵的大小,$\frac{1}{2}n$是半行,$-1$是和$1$作差。
它启示我们要把矩阵分成两部分。
然后怎么填呢?把前一半的数在左半边从小到大依次从左往右,从上到下填入矩阵,对于右半部分类似。
这样可以发现每一行的最大值和最小值值之差是$\frac{1}{2}n^{2} + \frac{1}{2}n - 1$,每一列的最大值与最小值之差是$\frac{1}{2}n^{2} - \frac{n}{2}$。
我们怎么来证明呢?
考虑设$S = \frac{1}{2}n^{2} + \frac{1}{2}n - 1$把所有数分成两类$1 ~ x$和$(x + S) ~ n^{2}$,其他数暂时不管。
为了证明答案,取$x = \frac{1}{4}n^{2} - \frac{1}{2}n + 1$。
- 当两个属于不同集合存在于同一行或同一列的时候,答案大于等于$S$。
- 假设第一类的数占据了$a$行和$b$列, 显然$ab \geqslant x$,然后有$a + b\geqslant \sqrt{ab} \geqslant 2\sqrt{x} = \sqrt{n^2 - 2n + 2} > n - 1$,即$a + b \geqslant n$。
- 假设第二类的数占据了$c$行和$d$列,显然$cd \geqslant n^{2} - x - S + 1 = \frac{1}{4}n^{2} + 1$,然后有$c + d\geqslant \sqrt{cd} \geqslant 2 \sqrt{ \frac{1}{4}n^2 + 1} = \sqrt{n^{2} + 4} > n$
- 所以$a + b + c + d > 2n$,由抽屉原理可知必然存在一个元素存在于同一行或同一列。
Code
1 #include <iostream> 2 #include <cstdlib> 3 #include <cstdio> 4 using namespace std; 5 typedef bool boolean; 6 7 int n; 8 inline void init() { 9 scanf("%d", &n); 10 } 11 12 inline void solve() { 13 if (n <= 600) { 14 int m = n >> 1; 15 for (int i = 1; i <= n; i++) { 16 int s = (i - 1) * m; 17 for (int j = 1; j <= m; j++) 18 printf("%d ", s + j); 19 s += (m * m) * 2; 20 for (int j = 1; j <= m; j++) 21 printf("%d ", s + j); 22 putchar(10); 23 } 24 } 25 cout << ((n * 1ll * n) / 2 + n / 2 - 1) << endl; 26 } 27 28 int T; 29 int main() { 30 scanf("%d", &T); 31 while (T--) { 32 init(); 33 solve(); 34 } 35 return 0; 36 }
Problem B Berserker的power
题目大意
有$n$个随机变量$x_{1}, x_{2}, \cdots, x_{n}$,第$i$个随机变量等概率取到$[l_{i}, r_{i}]$间的任意一个实数。问$\max((\sum_{i = 1}^{n}x_{i})^{m}, a^{m})$的期望。
取个$\max$纯属增加代码量。
设$f_{i}(x)$表示只考虑前$i$个随机变量的和为$x$的概率。每加入一个数,相当于对前面的某一段闭区间做了一次积分,然后可以拆成"前缀和"相减的形式,递归下去,变成在$f_{1}(x)$若干段区间做$n$阶积分。然后分$m$奇偶性讨论。
好了嘴巴AC了。改天再写代码。
Problem C Assassin的森林
题目大意
问$n$个点的带标号基环树森林的个数。
(纪中的多项式板子的运行效率咋集体被我们爆踩?)
感觉不是很难,但比较套路。
显然,考虑维护一下这么几个东西:
$f_{n,k}$表示$n$个点且基环大小为$k$的基环树个数。
$g_{n}$表示$n$个点的基环树个数。
$h_{n}$表示$n$个点的基环树森林个数。
转移都很简单啦。
$f_{n, k} = \frac{1}{2k}\sum_{i = 1}^{n}f_{n - i, k - 1}C_{n - 1}^{i - 1}i^{i - 1}$。
解释就是枚举当前向环上加入的有根树的大小,然后考虑拿出哪些点,为了防止算重,强行要求包含标号最大的点。由于这个形成一条有向链套树,变成环套树需要除以$2k$。
$g_{n} = \sum_{k = 3}^{n}f_{n,k} + n^{n - 2}$。
$h_{n} = \sum_{k = 1}^{n}g_{k}h_{n - k}C_{n - 1}^{k - 1}$。
于是我们得到了30分的高分。
然后考虑用生成函数来优化这个做法。
第一部分考虑我们其实是将一堆带标号有根树拼成有根链,然后变成环。
设有根树的指数生成函数是$F^{(e)}(x) = \sum_{i = 1}^{\infty} \frac{1}{i!}i^{i - 1}x^{i}$。
我们考虑这样一个指数生成函数
$G^{(e)}(x) = \sum_{n = 0}^{\infty} \frac{(n - 1)!F^{(e)}(x)^{n}}{2(n!)}=\frac{1}{2}\sum_{n=0}^{\infty}\frac{F^{(e)}(x)^{n}}{n} = -\frac{1}{2}\ln(1 - F^{(e)}(x))$
发现减去$n = 1, n = 2$的两项后,每一项再补上$n^{n - 2}$后就是我们想要的$g_{n}$的指数生成函数。
剩下就很简单,直接求exp就得到$h_{n}$的指数生成函数。
Code
1 #include <bits/stdc++.h> 2 using namespace std; 3 typedef bool boolean; 4 5 const int N = 262144; 6 const int bzmax = 20; 7 const int M = 998244353; 8 const int g = 3; 9 const int inv2 = (M + 1) >> 1; 10 11 int qpow(int a, int p) { 12 if (p < 0) 13 p += M - 1; 14 int rt = 1, pa = a; 15 for ( ; p; p >>= 1, pa = pa * 1ll * pa % M) 16 if (p & 1) 17 rt = rt * 1ll * pa % M; 18 return rt; 19 } 20 21 int add(int a, int b) { 22 return ((a += b) >= M) ? (a - M) : (a); 23 } 24 25 int sub(int a, int b) { 26 return ((a -= b) < 0) ? (a + M) : (a); 27 } 28 29 class NTT { 30 public: 31 int gn[bzmax], _gn[bzmax]; 32 33 NTT() { 34 for (int i = 0, L = 2; i < bzmax; i++, L <<= 1) { 35 gn[i] = qpow(g, (M - 1) / L); 36 _gn[i] = qpow(g, -(M - 1) / L); 37 } 38 } 39 40 void operator () (int* f, int len, int sgn) { 41 for (int i = 1, j = len >> 1, k; i < len - 1; i++, j += k) { 42 if (i < j) 43 swap(f[i], f[j]); 44 for (k = len >> 1; j >= k; j -= k, k >>= 1); 45 } 46 47 for (int b = 2, t = 0; b <= len; b <<= 1, t++) { 48 int wn = ((sgn > 0) ? (gn[t]) : (_gn[t])), w = 1, hb = b >> 1; 49 for (int i = 0; i < len; i += b, w = 1) 50 for (int j = i; j < i + hb; j++, w = w * 1ll * wn % M) { 51 int a = f[j], b = f[j + hb] * 1ll * w % M; 52 f[j] = add(a, b); 53 f[j + hb] = sub(a, b); 54 } 55 } 56 57 if (sgn < 0) { 58 int invlen = qpow(len, -1); 59 for (int i = 0; i < len; i++) 60 f[i] = f[i] * 1ll * invlen % M; 61 } 62 } 63 64 int correctLen(int n) { 65 int m = 1; 66 while (m < n) m <<= 1; 67 return m; 68 } 69 }NTT; 70 71 template<typename T> 72 void pcopy(T* ns, T* ne, const T* os) { 73 for ( ; ns != ne; *ns = *os, ns++, os++); 74 } 75 76 template<typename T> 77 void pfill(T* ps, T* pt, T val) { 78 for ( ; ps != pt; *ps = val, ps++); 79 } 80 81 void debug(const int* f, int n) { 82 for (int i = 0; i < n; i++) 83 cerr << f[i] << " "; 84 cerr << endl; 85 } 86 87 void debugE(const int *f, int n) { 88 int prod = 1; 89 for (int i = 0; i < n; i++) 90 cerr << f[i] * 1ll * prod % M << " ", prod = prod * 1ll * (i + 1) % M; 91 cerr << endl; 92 } 93 94 void pol_inverse(const int *f, int *g, int n) { 95 static int h[N]; 96 if (n == 1) 97 g[0] = qpow(f[0], -1); 98 else { 99 pol_inverse(f, g, (n + 1) >> 1); 100 101 int t = NTT.correctLen(n << 1 | 1); 102 pcopy(h, h + n, f); 103 pfill(h + n, h + t, 0); 104 NTT(h, t, 1); 105 NTT(g, t, 1); 106 for (int i = 0; i < t; i++) 107 g[i] = g[i] * 1ll * sub(2, g[i] * 1ll * h[i] % M) % M; 108 NTT(g, t, -1); 109 pfill(g + n, g + t, 0); 110 } 111 } 112 113 void diff(int *f, int n) { 114 for (int i = 1; i < n; i++) 115 f[i - 1] = f[i] * 1ll * i % M; 116 f[n - 1] = 0; 117 } 118 119 void integ(int *f, int n) { 120 for (int i = n; i; i--) 121 f[i] = f[i - 1] * 1ll * qpow(i, -1) % M; 122 f[0] = 0; 123 } 124 125 void pol_ln(const int* f, int* g, int n) { 126 static int A[N], B[N]; 127 int t = NTT.correctLen(n << 1 | 1); 128 pfill(g, g + t, 0); 129 pcopy(A, A + n, f); 130 pfill(A + n, A + t, 0); 131 diff(A, n); 132 pfill(B, B + t, 0); 133 pol_inverse(f, B, n); 134 NTT(A, t, 1); 135 NTT(B, t, 1); 136 for (int i = 0; i < t; i++) 137 g[i] = A[i] * 1ll * B[i] % M; 138 NTT(g, t, -1); 139 pfill(g + n - 1, g + t, 0); 140 integ(g, n); 141 } 142 143 void pol_exp(int* f, int* g, int n) { 144 static int A[N]; 145 if (n == 1) 146 g[0] = 1; 147 else { 148 int t = NTT.correctLen(n << 1 | 1); 149 pol_exp(f, g, (n + 1) >> 1); 150 151 pol_ln(g, A, n); 152 for (int i = 0; i < n; i++) 153 A[i] = sub(f[i], A[i]); 154 A[0] = add(A[0], 1); 155 NTT(g, t, 1); 156 NTT(A, t, 1); 157 for (int i = 0; i < t; i++) 158 g[i] = g[i] * 1ll * A[i] % M; 159 NTT(g, t, -1); 160 pfill(g + n, g + t, 0); 161 } 162 } 163 164 int n; 165 int Fe[N], rFe[N], Ge[N]; 166 int fac[N]; 167 168 inline void init() { 169 scanf("%d", &n); 170 fac[0] = 1; 171 for (int i = 1; i <= n; i++) 172 fac[i] = fac[i - 1] * 1ll * i % M; 173 for (int i = 1; i <= n; i++) 174 Fe[i] = qpow(fac[i], -1) * 1ll * qpow(i, i - 1) % M; 175 for (int i = 1; i <= n; i++) 176 rFe[i] = M - Fe[i]; 177 rFe[0] = 1; 178 } 179 180 inline void solve() { 181 pol_ln(rFe, Ge, n + 1); 182 for (int i = 1; i <= n; i++) 183 Ge[i] = sub(sub(0, Ge[i]), Fe[i]); 184 int t = NTT.correctLen((n + 1) << 1); 185 NTT(Fe, t, 1); 186 for (int i = 0; i < t; i++) 187 Fe[i] = Fe[i] * 1ll * Fe[i] % M; 188 NTT(Fe, t, -1); 189 for (int i = 0; i <= n; i++) 190 Ge[i] = sub(Ge[i], Fe[i] * 1ll * inv2 % M) * 1ll * inv2 % M; 191 for (int i = 0; i <= n; i++) 192 Ge[i] = add(Ge[i], qpow(i, i - 2) * 1ll * qpow(fac[i], -1) % M); 193 pfill(Fe, Fe + t, 0); 194 pol_exp(Ge, Fe, n + 1); 195 printf("%d\n", Fe[n] * 1ll * fac[n] % M); 196 } 197 198 int main() { 199 init(); 200 solve(); 201 return 0; 202 }