[Luogu]P5387 [Cnoi2019]人形演舞(FWT,SG函数)


刚好FWT和SG函数都刚学,这道题也挺模板的,就拉来做做。
手动打个SG函数表发现\(sg[2^k+n]=n+1\),然后博弈论就被干掉了,剩下的问题变成,有\(m\)个数可以选,选择\(v\)个数使得异或和为\(0\)
这道题题意有锅吧,正确表述应该是大小为\(V\)的序列,而不是集合,因为方案数跟选取顺序有关。
\(A_k[i]\)表示选取\(k\)个数,异或和为\(i\)的方案数。
那么\(A_{k + 1}[c] = \sum_{a\oplus b = c} A_k[a]\times A_1[b]\),定义乘法为卷积,这样答案就是\(A_1^v\),快速幂搞一下就行。

#include 
#define int long long
#define pt(x) cout << x << endl;
#define Mid ((l + r) / 2)
#define lson (rt << 1)
#define rson (rt << 1 | 1)
using namespace std;
int read() {
	char c; int num, f = 1;
	while(c = getchar(),!isdigit(c)) if(c == '-') f = -1; num = c - '0';
	while(c = getchar(), isdigit(c)) num = num * 10 + c - '0';
	return f * num;
}
const int N = 2e6 + 1009;
const int mod = 998244353;
int n, m, sg[N], A[N], B[N];
int Pow(int a, int p) {
	int ans = 1;
	for( ; p; p >>= 1, a = a * a % mod)
		if(p & 1)
			ans = ans * a % mod;
	return ans % mod;
}
void FWT(int *A, int n, int type) {
	int inv_2 = Pow(2, mod - 2);
	for(int m = 1; m < n; m <<= 1) {
		for(int i = 0; i < n; i += 2 * m) {
			for(int j = 0; j < m; j++) {
				int x = A[i + j], y = A[i + j + m];
				A[i + j] = (1ll * x + y) * (type == 1 ? 1 : inv_2) % mod;
				A[i + j + m] = (1ll * x - y + mod) * (type == 1 ? 1 : inv_2) % mod;
			}
		}
	}
}
void Arrtimes(int *A, int lim, int *B) {
	for(int i = 0; i < lim; i++)
		A[i] = A[i] * B[i] % mod;
}
void FWTPow(int *A, int p, int *B, int lim) {
	for(int i = 0; i < lim; i++) B[i] = 1;
	FWT(A, lim, 1);
	for( ; p; p >>= 1, Arrtimes(A, lim, A))
		if(p & 1)
			Arrtimes(B, lim, A);
	FWT(B, lim, -1);
}
signed main()
{
	n = read(); m = read();
	for(int i = 0; i <= m; i++) 
		sg[i] = i - (1 << (32 - __builtin_clz(i) - 1)) + 1;
	for(int i = 1; i <= m; i++) A[sg[i]]++;
	int lim = 1;
	while(lim <= m) lim <<= 1;
	FWTPow(A, n, B, lim);
	printf("%lld\n", B[0]);
	
	printf("%lld\n", (Pow(m, n) - B[0] + mod) % mod);
	return 0;
}