Project Euler 798

Project Euler 798

题目

Card Stacking Game

Two players play a game with a deck of cards which contains $s$ suits with each suit containing $n$ cards numbered from $1$ to $n$.

Before the game starts, a set of cards (which may be empty) is picked from the deck and placed face-up on the table, with no overlap. These are called the visible cards.

The players then make moves in turn.

A move consists of choosing a card $X$ from the rest of the deck and placing it face-up on top of a visible card $Y$, subject to the following restrictions:

  • $X$ and $Y$ must be the same suit;
  • the value of $X$ must be larger than the value of $Y$.

The card $X$ then covers the card $Y$ and replaces $Y$ as a visible card.

The player unable to make a valid move loses and play stops.

Let $C(n, s)$ be the number of different initial sets of cards for which the first player will lose given best play for both players.

For example, $C(3, 2) = 26$ and $C(13, 4) \equiv 540318329 \pmod {1\,000\,000\,007}$.

Find $C(10^7, 10^7)$. Give your answer modulo $1\,000\,000\,007$.

解决方案

本解决方案参考了Thread中的内容,一开始我是直接暴力加FWT做出本题的。

令$N=M=10^7$。每一个花色可以看成是一个局面。整个游戏由多个不同花色组合而成,因此通过SG定理将最终游戏的SG函数值组合出来。由于$M$值过大,需要使用快速沃尔什变换解决异或卷积。

那么对于单个花色而言,一共有$N$张牌,因此初始状态一共有$2^N$种,每种初始状态对应一个${1,2,\dots,N}$的子集。

枚举$2^N$个子集的$sg$函数值并统计个数。那么当$N=1,2,\dots,14$时,结果如下(从第$0$列起,第$j$列表示有多少个子集的$sg$函数值为$j$):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
   2
3 1
4 3 1
6 6 3 1
10 11 6 4 1
18 20 11 10 4 1
34 37 20 21 10 5 1
66 70 37 41 21 15 5 1
130 135 70 78 41 36 15 6 1
258 264 135 148 78 77 36 21 6 1
514 521 264 283 148 155 77 57 21 7 1
1026 1034 521 547 283 303 155 134 57 28 7 1
2050 2059 1034 1068 547 586 303 289 134 85 28 8 1
4098 4108 2059 2102 1068 1133 586 592 289 219 85 36 8 1

将每一行都逆序,并且最后一个值都减去$1$,得到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1
1 2
1 3 3
1 3 6 5
1 4 6 11 9
1 4 10 11 20 17
1 5 10 21 20 37 33
1 5 15 21 41 37 70 65
1 6 15 36 41 78 70 135 129
1 6 21 36 77 78 148 135 264 257
1 7 21 57 77 155 148 283 264 521 513
1 7 28 57 134 155 303 283 547 521 1034 1025
1 8 28 85 134 289 303 586 547 1068 1034 2059 2049
1 8 36 85 219 289 592 586 1133 1068 2102 2059 4108 4097

对于每一列(除了第$0$列)而言,从下面的数开始,都是两两出现,去掉其中一个,那么可以简化成:

1
2
3
4
5
6
7
8
9
10
(A)

1
1 2
1 3 3
1 4 6 5
1 5 10 11 9
1 6 15 21 20 17
1 7 21 36 41 37 33
1 8 28 57 77 78 70 65

可以发现,这个三角的产生方式和杨辉三角相同,只是边界值不一样。

舍去第一行,将其右上方进行补全,得到:

1
2
3
4
5
6
7
1  2  1  1  1  1  1  1
1 3 3 2 2 2 2 2
1 4 6 5 4 4 4 4
1 5 10 11 9 8 8 8
1 6 15 21 20 17 16 16
1 7 21 36 41 37 33 32
1 8 28 57 77 78 70 65

最终发现上面这个图形矩阵可以由杨辉三角的偏移叠加得到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
 1  0  0  0  0  0  0  0
1 1 0 0 0 0 0 0
1 2 1 0 0 0 0 0
1 3 3 1 0 0 0 0
1 4 6 4 1 0 0 0
1 5 10 10 5 1 0 0
1 6 15 20 15 6 1 0

0 2 0 0 0 0 0 0
0 2 2 0 0 0 0 0
0 2 4 2 0 0 0 0
0 2 6 6 2 0 0 0
0 2 8 12 8 2 0 0
0 2 10 20 20 10 2 0
0 2 12 30 40 30 12 2

0 0 1 0 0 0 0 0
0 0 1 1 0 0 0 0
0 0 1 2 1 0 0 0
0 0 1 3 3 1 0 0
0 0 1 4 6 4 1 0
0 0 1 5 10 10 5 1
0 0 1 6 15 20 15 6

0 0 0 1 0 0 0 0
0 0 0 1 1 0 0 0
0 0 0 1 2 1 0 0
0 0 0 1 3 3 1 0
0 0 0 1 4 6 4 1
0 0 0 1 5 10 10 5
0 0 0 1 6 15 20 15

...

总之,$2^N$个子集中,$sg$函数值为$k$的子集个数为$f\left(N-k-1,\left\lceil\dfrac{k}{2}\right\rceil\right)$,其中$f(x,y)$表示三角形数组(A)一开始在第$x$列的第一个元素,然后再往下走$y$步后的元素。(注意当$k=0$时,需要将值添加回$1$。)

那么根据刚刚的思想,有

最终,使用等式$Ci^j=C{i-1}^{j-1}+C_i^j$这个等式,在求$f\left(N-k-1,\left\lceil\dfrac{k}{2}\right\rceil\right)$时,将求和项的相邻两项合并。并且由于运算$\lceil\rceil$运算的存在,合并之后的结果也是连续的,通过这个现象可以$O(N)$维护出$f\left(N-k-1,\left\lceil\dfrac{k}{2}\right\rceil\right)$的所有值。最终使用快速沃尔什变换计算出最终答案。

代码

本代码参考了Thread中的内容并重新实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <bits/stdc++.h>
#include "tools.h"
using namespace std;
typedef long long ll;
const int N = 10000000;
const int M = 10000000;
ll mod=1000000007;
ll fac[N+4],inv[N+4],finv[N+4];
ll C(int n,int m){
return fac[n]*finv[m]%mod*finv[n-m]%mod;
}
int main(){
fac[0]=fac[1]=inv[1]=finv[0]=finv[1]=1;
for(int i=2;i<=N;i++){
fac[i]=fac[i-1]*i%mod;
inv[i]=(mod-mod/i)*inv[mod%i]%mod;
finv[i]=finv[i-1]*inv[i]%mod;
}
vector<int>b(N);
b[0]=1;
for(int k=1;k<N;k++){
b[k]=(b[k-1]+C((N+k)/2-1,k))%mod;
if(k>=2&&(N+k)%2==0) b[k]=(b[k]+b[k-2])%mod;
}
for(int k=0;k<N;k++)
b[k]=(b[k]+C((N+k)/2-1,k-1))%mod;
b[N-1]++;
reverse(b.begin(),b.end());
fwt.init_mod(mod);
int ans=fwt.powXor(b,M)[0];
printf("%d\n",ans);
}