【luogu CF838C】Future Failure(博弈论)(子集卷积)

Future Failure

题目链接:luogu CF838C

题目大意

两个人对着一个字符串博弈,每次可以重新排列这个字符串或者删去一个字符。然后每次得到的字符串要是之前没有出现过的,谁无法操作谁就输了。
然后问你长度为 n 的字符串,只能用 k 种字符,有多少个串先手必胜。

思路

首先我们博弈一下,会发现一个状态能赢要么是它拿走一个存在一个子串能赢,要么就是它这个串的排列方式是偶数。

那考虑那排列方式列出来:
n!a1!a2!a3!...ak!ai 表示第 i 个字符的出现次数)
那你考虑删了一个数会改变多少如果删的是第 i 个字符,那就是乘上 ain

如果 n 是奇数,必定有一个 ai 也是奇数,那你可以删使得奇偶不变。

然后会发现 n 是奇数直接必胜,因为如果删了能赢就删,否则就可以不停的排,那后手也一定不能删,也只能排,那会发现如果是不能删的情况一定是偶数中排列方式的。
(可以通过上面那个奇偶不变得到)

那如果是偶数就不能让自己走到奇数,所以就是只用它自己的排列方式是偶数。
那我们接下来就是考虑统计这个偶数的,发现不好统计,我们考虑统计奇数的,就是 n! 质因数分解的每个 2 都可以跟下面的 2 对应。
那考虑统计 2 质因子的个数,先考虑一个阶乘怎么求。
n!in2i

in2i=x=1kiax2i

然后我们考虑不停分解 x 出来,因为上的数量肯定比下面的多,那我们就把这个情况看到每个 2i 中,这都应该是要满足的,所以它们每一位应该都是一样的:
iN,n2i=iax2i
那你就考虑这个是什么鬼,那我们会发现这个其实跟二进制有关,就是所有 ax 加在一起不能有进位。

那这个其实我们不难想到一个东西就是把 n 的二进制拆成若干份,每个 x 贡献是 1x!,然后每个的贡献乘起来,最后另外乘上 n!
然后你这个乘你可以用循环卷积来做,就 k 个字符依次加入然后 FWT 一下。
然后这样还是太慢所以可以 k 这个地方快速幂一下。

代码

#include<cstdio> #define ll long long using namespace std; const int N = 1 << 19; int n, k, mo, jc[N], inv[N], num1[N]; int f[20][N], g[20][N]; int jia(int x, int y) {return x + y >= mo ? x + y - mo : x + y;} int jian(int x, int y) {return x < y ? x - y + mo : x - y;} int cheng(int x, int y) {return 1ll * x * y % mo;} int ksm(int x, int y) { int re = 1; while (y) { if (y & 1) re = cheng(re, x); x = cheng(x, x); y >>= 1; } return re; } void Init() { jc[0] = 1; for (int i = 1; i < N; i++) jc[i] = cheng(jc[i - 1], i); inv[0] = inv[1] = 1; for (int i = 2; i < N; i++) inv[i] = cheng(inv[mo % i], (mo - mo / i)); for (int i = 1; i < N; i++) inv[i] = cheng(inv[i - 1], inv[i]); for (int i = 1; i < N; i++) num1[i] = num1[i ^ (i & (-i))] + 1; } void FWT(int *f, int limit, int op) { for (int mid = 1; mid < limit; mid <<= 1) { for (int R = mid << 1, j = 0; j < limit; j += R) { for (int k = 0; k < mid; k++) { ll x = f[j | k], y = f[j | mid | k]; f[j | k] = x; f[j | mid | k] = jia(cheng(x, op), y); } } } } int work(int n, int k) { int m = 1; while (m <= n) m <<= 1; f[0][0] = 1; for (int i = 0; i <= n; i++) g[num1[i]][i] = inv[i]; for (int i = 0; i < num1[n]; i++) FWT(g[i], m, 1); FWT(f[0], m, 1); for (int kk = k; kk; kk >>= 1) { if (kk & 1) { for (int i = num1[n]; i >= 0; i--) { for (int j = 0; j < m; j++) f[i][j] = cheng(f[i][j], g[0][j]); for (int j = 0; j < i; j++) for (int k = 0; k < m; k++) { f[i][k] = jia(f[i][k], cheng(f[j][k], g[i - j][k])); } } } for (int i = num1[n]; i >= 0; i--) { for (int j = 0; j < m; j++) g[i][j] = cheng((i ? 2 : 1), cheng(g[i][j], g[0][j])); for (int j = 1; j < i; j++) for (int k = 0; k < m; k++) { g[i][k] = jia(g[i][k], cheng(g[j][k], g[i - j][k])); } } } FWT(f[num1[n]], m, mo - 1); return cheng(f[num1[n]][n], jc[n]); } int main() { scanf("%d %d %d", &n, &k, &mo); if (n & 1) { printf("%d", ksm(k, n)); return 0; } Init(); printf("%d", jian(ksm(k, n), work(n, k))); return 0; }

__EOF__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/luogu_CF838C.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(35)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示