【luogu P5363】移动金币(博弈论)(DP)(数位DP)(MTT)

移动金币

题目链接:luogu P5363

题目大意

给你一个 1*n 的棋盘,然后一开始有 m 个东西,每次两个人轮流操作,可以选一个东西往左移若干格。
(不能飞出格子,不能越过别的东西)
然后谁不能动就输了,然后问你有多少种初始情况先手必胜。

思路

首先考虑推出什么时候会赢。

你可以把一个点往左移看做是它和它前面的点的距离变小,到它后面点的距离变大,然后你再把 0 也看做一个点(它不可以移),那如果所有相邻的距离都是 0 就输了。

然后把一个变小若干,把旁边的变大若干,可以看做是把左边的若干个式子移动到右边,然后全部移动到最右边就输了。

那就是一个阶梯博弈,就是要奇数位的石子个数异或和。
那必胜是非 0 不好搞考虑求是 0 的。

然后考虑 DP,就是要把 nm 个石子放到 m+1 堆里面且偶数堆异或和是 0
首先是暴力枚举每一堆放多少个石子状态时到第 i 堆一共用了多少式子当前异或值是多少,mn3

然后考虑异或,就每位每位搞,就每次看一位,看有多少堆里面放了。
fi,j 为当前处理到第 i 位,然后已经有了 j 的石子,然后枚举放多少个,nmlogn

然后发现你对于 N=nm,对于它二进制的每一位,要么是在这个位数放了一个式子,要么是从低位的放了若干个进位过来的。
那我们可以直接设 fi,j 为处理到第 i 位,当前为还有 j 次可以选。
然后因为如果 j>m+1 就直接无解(无论如何都放不完),所以 jm 级别的,m2logn

具体转移大概就是:fi,2j+vkfi+1,jpkvN 这一位是 1 还是 0pkk 个分给 m+1 堆,偶数堆有偶数个的方案数)

pk 可以预处理,就直接暴力枚举给偶数堆的个数 j,直接 Cjm+12Ckjm+1m+12
m+1m+12=m+22

然后其实就可以了。


但是还不够(至少在对于某题来讲)

然后你发现转移的式子它其实可以看做是卷起来。
fi+1,j 放到 a2j+v 的位置,pk 放到 bm+1k 的位置。
然后 ab 的卷积的第 i 位结果就是 fi,i(m1) 了。

然后你用任意模数 NTT 搞即可,这里用了 MTT 的方法。

然后复杂度就是 mlogmlogn 了。

代码

普通版(m2logn

#include<cstdio> #define ll long long #define mo 1000000009 using namespace std; int n, m; ll jc[61], inv[61], p[61]; ll f[21][61]; ll C(int n, int m) { if (n < m) return 0; if (m < 0) return 0; return jc[n] * inv[m] % mo * inv[n - m] % mo; } ll ksm(ll x, ll y) { ll re = 1; while (y) { if (y & 1) re = re * x % mo; x = x * x % mo; y >>= 1; } return re; } void init() { jc[0] = 1; for (int i = 1; i <= m + 1; i++) jc[i] = jc[i - 1] * i % mo; inv[m + 1] = ksm(jc[m + 1], mo - 2); for (int i = m; i >= 0; i--) inv[i] = inv[i + 1] * (i + 1) % mo; int on = (m + 1) / 2, jn = (m + 2) / 2; for (int k = 0; k <= m + 1; k++) { for (int i = 0; i <= on; i += 2) { p[k] = (p[k] + C(on, i) * C(jn, k - i) % mo) % mo; } } } int main() { scanf("%d %d", &n, &m); init(); f[20][0] = 1; int need = n - m; for (int i = 19; i >= 0; i--) { int hv = (need >> i) & 1; for (int j = 0; j <= m + 1; j++) for (int k = 0; k <= m + 1; k++) { int to = 2 * j + hv - k; if (to < 0 || to > m + 1) continue; f[i][to] = (f[i][to] + f[i + 1][j] * p[k] % mo) % mo; } } ll ans = 1; for (int i = n - m + 1; i <= n; i++) ans = ans * i % mo; ans = ans * inv[m]; printf("%lld", (ans - f[0][0] + mo) % mo); return 0; }

MTT版(mlogmlogn

#include<cmath> #include<cstdio> #include<cstring> #include<algorithm> #define lim 32000 #define ll long long #define mo 1000000009 using namespace std; struct complex { double x, y; complex (double xx = 0, double yy = 0) { x = xx; y = yy; } }; int n, m; ll jc[61], inv[61], p[61]; ll f[21][61], ans[201], a[201], b[201]; double Pi = acos(-1.0); complex operator +(complex x, complex y) { return (complex){x.x + y.x, x.y + y.y}; } complex operator -(complex x, complex y) { return (complex){x.x - y.x, x.y - y.y}; } complex operator *(complex x, complex y) { return (complex){x.x * y.x - x.y * y.y, x.x * y.y + x.y * y.x}; } ll C(int n, int m) { if (n < m) return 0; if (m < 0) return 0; return jc[n] * inv[m] % mo * inv[n - m] % mo; } ll ksm(ll x, ll y) { ll re = 1; while (y) { if (y & 1) re = re * x % mo; x = x * x % mo; y >>= 1; } return re; } void init() { jc[0] = 1; for (int i = 1; i <= m + 1; i++) jc[i] = jc[i - 1] * i % mo; inv[m + 1] = ksm(jc[m + 1], mo - 2); for (int i = m; i >= 0; i--) inv[i] = inv[i + 1] * (i + 1) % mo; int on = (m + 1) / 2, jn = (m + 2) / 2; for (int k = 0; k <= m + 1; k++) { for (int i = 0; i <= on; i += 2) { p[k] = (p[k] + C(on, i) * C(jn, k - i) % mo) % mo; } } } struct MTT_work { complex p1[201 << 2], p2[201 << 2], q[201 << 2]; int limit, l_size, an[201 << 2]; void FFT(complex *now, int op) { for (int i = 0; i < limit; i++) if (i < an[i]) swap(now[i], now[an[i]]); for (int mid = 1; mid < limit; mid <<= 1) { complex Wn(cos(Pi / mid), op * sin(Pi / mid)); for (int R = (mid << 1), j = 0; j < limit; j += R) { complex w(1, 0); for (int k = 0; k < mid; k++, w = w * Wn) { complex x = now[j + k], y = w * now[j + mid + k]; now[j + k] = x + y; now[j + mid + k] = x - y; } } } } void mul(int n, ll *x, int m, ll *y) { limit = 1; l_size = 0; while (limit <= n + m) { limit <<= 1; l_size++; } for (int i = 0; i < limit; i++) p1[i] = p2[i] = q[i] = (complex){0, 0}; for (int i = 0; i <= n; i++) p1[i] = (complex){x[i] / lim, x[i] % lim}, p2[i] = (complex){x[i] / lim, -x[i] % lim}; for (int i = 0; i <= m; i++) q[i] = (complex){y[i] / lim, y[i] % lim}; for (int i = 0; i < limit; i++) an[i] = (an[i >> 1] >> 1) | ((i & 1) << (l_size - 1)); FFT(p1, 1); FFT(p2, 1); FFT(q, 1); for (int i = 0; i < limit; i++) q[i].x /= limit, q[i].y /= limit; for (int i = 0; i < limit; i++) p1[i] = p1[i] * q[i], p2[i] = p2[i] * q[i]; FFT(p1, -1); FFT(p2, -1); for (int i = 0; i <= n + m; i++) { ll a1b1 = (ll)floor((p1[i].x + p2[i].x) / 2 + 0.5) % mo; ll a1b2 = (ll)floor((p1[i].y + p2[i].y) / 2 + 0.5) % mo; ll a2b1 = ((ll)floor(p1[i].y + 0.5) - a1b2) % mo; ll a2b2 = ((ll)floor(p2[i].x + 0.5) - a1b1) % mo; ans[i] = (a1b1 * lim * lim + (a1b2 + a2b1) * lim + a2b2) % mo; ans[i] = (ans[i] + mo) % mo; } } }MTT; int main() { scanf("%d %d", &n, &m); init(); f[20][0] = 1; int need = n - m; for (int i = 19; i >= 0; i--) { int hv = (need >> i) & 1; // for (int j = 0; j <= m + 1; j++) // for (int k = 0; k <= m + 1; k++) { // int to = 2 * j + hv - k; // if (to < 0 || to > m + 1) continue; // f[i][to] = (f[i][to] + f[i + 1][j] * p[k] % mo) % mo; // } memset(a, 0, sizeof(a)); memset(b, 0, sizeof(b)); for (int j = 0; j <= m + 1; j++)//两倍加上进位 a[2 * j + hv] = f[i + 1][j]; for (int j = 0; j <= m + 1; j++)//反过来全部右移 m+1 b[m + 1 - j] = p[j]; MTT.mul((m + 1) * 2 + hv, a, m + 1, b); for (int j = m + 1; j <= m + 1 + m + 1; j++) f[i][j - (m + 1)] = ans[j];//因为 b 右移了所以要移回来 m+1 位 } ll answ = 1; for (int i = n - m + 1; i <= n; i++) answ = answ * i % mo; answ = answ * inv[m]; printf("%lld", (answ - f[0][0] + mo) % mo); return 0; }

__EOF__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/luogu_P5363.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(117)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示