没这种事
没这种事。
中有奇数的情况,可以发现中心的那一列与外面完全无关,直接拎出来数一下答案之后,最后与 都是偶数的答案相乘就是原来的答案了。接下来只需要考虑 都是偶数的情况。
矩阵的变换显然只会对关于中心对称的四个点施加置换。我们可以玩出这样一个结论:对于这四个点,我们可以在不改变其它位置的情况下施加任意偶置换,所以说,每个组之间的联动性单纯只体现在置换的奇偶性上。
而且如果有一个组中有两个相同的元素,交换这两个元素改变了奇偶性却没改变矩阵,所以说有元素相同的组可以在不扰动其它位置的情况下任意排列。
我们考虑这个限制的形式是一张二分图:如果一个组元素互不相同,则在它最小的行编号与最小的列编号之间连一条边,一个局面能被原局面到达,当且仅当考虑给每条边标上对应置换的奇偶性之后,每一个环的边权和都是偶数。即将其视作异或方程组之后有解。那么一个局面能到达的局面数量只与它连出来的这张图的树边条数或者说连通块数有关。
二分图的连通块数,考虑先求 求出联通二分图的 EGF 之后乘上每个连通块的系数 回来。本题中系数是 ,所以这个过程可以完全简化成开根。
二维 EGF 开根怎么做?考虑 zhy 在 YAMMCP 中教育到的处理二元多项式的常见手段。
即如果我们想对二元 GF 做什么操作,考虑一元 GF 做这个操作时所需要的步骤,往往只需要多项式意义下的线性操作,也就是加、数乘和卷积。你当然可以内层封装一个实现多项式域运算操作的 struct,这样的复杂度一般可以做到 ,但是往往过于难写而且常数巨大,尤其是一元 GF 的 操作本来常数巨大十分难写难记,我们需要更加亲民的搞法。
有一种好写的、更加高效的想法。考虑二元 GF 较小的一维是根号规模的,所以这一维的部分可以上平方的递推做法,只有常数项一般需要快速做一次一元多项式操作求出来(本题多项式的常数项是 ,开个根自然是 ,直接展开该式就可以了,无需写真正的开根)。而内层也千万不要封装了,因为多数平方递推部分每次都 IDFT 回来太亏,我们见招拆招只在需要截断或者需要输出的时候才 IDFT 还原,其它时候的一直保持点值形态更加方便处理,常数极小。复杂度是根号级别的。
如果保证两位都是根号规模的,面对非费马质数的质数模数,我们可以直接使用求值插值代替 NTT,常数项处的一元多项式操作我们也可以使用平方递推代替。复杂度依然是根号级别的。
有趣的事实:zhy 提到的,将 换元成 ,这导出了一个不依赖费马质数的 多项式乘法算法,是不是很快,思路清晰之后这个做法还是非常好写的。
Karatsuba,你是不是彻底没用了。
记得特判二分图不连边的系数是 的情况,否则过不了 uoj 的 hack。
#include <algorithm> #include <cstdio> using namespace std; typedef long long ll; const int P = 998244353; const int N = 1 << 20; inline void inc(int &x, int v) { if ((x += v) >= P) x -= P; } inline void dec(int &x, int v) { if ((x -= v) < 0) x += P; } int qp(int a, int b = P - 2) { int res = 1; while (b) { if (b & 1) res = (ll)res * a % P; a = (ll)a * a % P; b >>= 1; } return res; } int n, m, k; int calcline(int x) { return (2ll * qp(k, 2 * x) - qp(k, x) + P) % P; } const int coe[4] = {1, 68, 432, 288}; const int iv[4] = {qp(1), qp(2), qp(3), qp(4)}; int ibn[N], bn[N], pw[N]; int *f[N], g[N]; int len, ilen, bt; int rev[N], cw[N | 1]; int fac[N], fiv[N]; void init(int _len) { // mod x^len len = 1, bt = -1; while (len <= _len) len <<= 1, ++bt; int w = qp(3, (P - 1) >> (bt + 1)); cw[0] = cw[len] = 1; for (int i = 1; i < len; ++i) { cw[i] = (ll)cw[i - 1] * w % P; rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << bt); } ilen = qp(len); } void NTT(int *F) { for (int i = 1; i < len; ++i) if (rev[i] < i) swap(F[rev[i]], F[i]); for (int i = 1, tt = len >> 1; i < len; i <<= 1, tt >>= 1) for (int j = 0; j < len; j += (i << 1)) for (int k = j, t = 0; k < (j | i); ++k, t += tt) { int x = F[k], y = (ll)F[k | i] * cw[t] % P; if ((F[k] = x + y) >= P) F[k] -= P; if ((F[k | i] = x - y) < 0) F[k | i] += P; } } void INTT(int *F) { for (int i = 1; i < len; ++i) if (rev[i] < i) swap(F[rev[i]], F[i]); for (int i = 1, tt = len >> 1; i < len; i <<= 1, tt >>= 1) for (int j = 0; j < len; j += (i << 1)) for (int k = j, t = len; k < (j | i); ++k, t -= tt) { int x = F[k], y = (ll)F[k | i] * cw[t] % P; if ((F[k] = x + y) >= P) F[k] -= P; if ((F[k | i] = x - y) < 0) F[k | i] += P; } for (int i = 0; i < len; ++i) F[i] = (ll)F[i] * ilen % P; } int main() { scanf("%d%d%d", &n, &m, &k); if (n > m) swap(n, m); int res = 1; if (n & 1) res = (ll)res * calcline(m >> 1) % P; if (m & 1) res = (ll)res * calcline(n >> 1) % P; if (n & m & 1) res = (ll)res * k % P; n >>= 1, m >>= 1; if (!n or !m) { printf("%d\n", res); return 0; } int c0 = 0, c1 = 0; for (int i = 0, now = 1; i < 4; ++i) { now = (ll)now * (k - i) % P * iv[i] % P; if (i < 3) inc(c0, (ll)now * coe[i] % P); else inc(c1, (ll)now * coe[i] % P); } if (c0) c1 = (ll)c1 * qp(c0) % P; int sum = 0; fac[0] = pw[0] = ibn[0] = bn[0] = 1; for (int i = 1; i <= n * m; ++i) pw[i] = (ll)pw[i - 1] * (c1 + 1) % P; for (int i = 1; i <= m; ++i) { fac[i] = (ll)fac[i - 1] * i % P; ibn[i] = ibn[i - 1] * 499122176ll % P; inc(bn[i] = bn[i - 1], bn[i - 1]); } fiv[m] = qp(fac[m]); for (int i = m; i; --i) fiv[i - 1] = (ll)fiv[i] * i % P; init(m << 1); for (int i = 0; i <= n; ++i) f[i] = new int[len](); for (int i = 0; i <= m; ++i) f[0][i] = 499122177ll * fiv[i] % P * ibn[i] % P; NTT(f[0]); for (int i = 1; i <= n; ++i) { for (int x = 0; x < len; ++x) g[x] = 0; for (int j = 1; j < i; ++j) for (int x = 0; x < len; ++x) inc(g[x], (ll)f[j][x] * f[i - j][x] % P); INTT(g); for (int x = 0; x <= m; ++x) dec(f[i][x] = (ll)fiv[i] * fiv[x] % P * pw[i * x] % P, g[x]); NTT(f[i]); for (int x = 0; x < len; ++x) f[i][x] = (ll)f[i][x] * f[0][x] % P; INTT(f[i]); for (int x = m + 1; x < len; ++x) f[i][x] = 0; if (i == n) { sum = (ll)f[n][m] * fac[n] % P * fac[m] % P; break; } NTT(f[i]); } if (c0) res = (ll)res * qp(c0, n * m) % P * sum % P * bn[n] % P * bn[m] % P; else res = (ll)res * qp(c1, n * m) % P * bn[n] % P * bn[m - 1] % P; printf("%d\n", res); return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现