1. 程式人生 > 其它 >【luogu CF838C】Future Failure(博弈論)(子集卷積)

【luogu CF838C】Future Failure(博弈論)(子集卷積)

Future Failure

題目連結:luogu CF838C

題目大意

兩個人對著一個字串博弈,每次可以重新排列這個字串或者刪去一個字元。然後每次得到的字串要是之前沒有出現過的,誰無法操作誰就輸了。
然後問你長度為 n 的字串,只能用 k 種字元,有多少個串先手必勝。

思路

首先我們博弈一下,會發現一個狀態能贏要麼是它拿走一個存在一個子串能贏,要麼就是它這個串的排列方式是偶數。

那考慮那排列方式列出來:
\(\dfrac{n!}{a_1!a_2!a_3!...a_k!}\)\(a_i\) 表示第 \(i\) 個字元的出現次數)
那你考慮刪了一個數會改變多少如果刪的是第 \(i\) 個字元,那就是乘上 \(\dfrac{a_i}{n}\)

如果 \(n\) 是奇數,必定有一個 \(a_i\) 也是奇數,那你可以刪使得奇偶不變。

然後會發現 \(n\) 是奇數直接必勝,因為如果刪了能贏就刪,否則就可以不停的排,那後手也一定不能刪,也只能排,那會發現如果是不能刪的情況一定是偶數中排列方式的。
(可以通過上面那個奇偶不變得到)

那如果是偶數就不能讓自己走到奇數,所以就是隻用它自己的排列方式是偶數。
那我們接下來就是考慮統計這個偶數的,發現不好統計,我們考慮統計奇數的,就是 \(n!\) 質因數分解的每個 \(2\) 都可以跟下面的 \(2\) 對應。
那考慮統計 \(2\) 質因子的個數,先考慮一個階乘怎麼求。
\(n!\rightarrow \sum\limits_{i}\left\lfloor\dfrac{n}{2^i}\right\rfloor\)

\(\sum\limits_{i}\left\lfloor\dfrac{n}{2^i}\right\rfloor=\sum\limits_{x=1}^k\sum\limits_{i}\left\lfloor\dfrac{a_x}{2^i}\right\rfloor\)

然後我們考慮不停分解 \(x\) 出來,因為上的數量肯定比下面的多,那我們就把這個情況看到每個 \(2^i\) 中,這都應該是要滿足的,所以它們每一位應該都是一樣的:
\(\forall i\in N,\left\lfloor\dfrac{n}{2^i}\right\rfloor=\sum\limits_{i}\left\lfloor\dfrac{a_x}{2^i}\right\rfloor\)


那你就考慮這個是什麼鬼,那我們會發現這個其實跟二進位制有關,就是所有 \(a_x\) 加在一起不能有進位。

那這個其實我們不難想到一個東西就是把 \(n\) 的二進位制拆成若干份,每個 \(x\) 貢獻是 \(\dfrac{1}{x!}\),然後每個的貢獻乘起來,最後另外乘上 \(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;
}