[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;
}