题目:
链接:https://www.nowcoder.com/acm/contest/73/F
Nim游戏是这样的:有 n 个石子堆,第 i 个石子堆有 a[i] 个石子,两个人轮流选择一个石子堆并从中拿走一个或者多个石子。拿走最后一个石子的人获胜。
现在有两个人他们决定每次随机选择一个合法决策来操作。现在他们想知道在这种决策方式下先手的胜率以及所有可能的情况中先手获胜的次数,对 998244353 取模。
即设答案化为最简分式后的形式为 a/b ,其中 a 和 b 的互质。 输出整数 x 使得
b
x
≡
a
m
o
d
998244353
bx ≡ a ~mod ~998244353
bx≡a mod 998244353 且
0
≤
x
<
998244353
0 ≤ x < 998244353
0≤x<998244353。 可以证明这样的整数 x 是唯一的。
输入描述:
第一行一个正整数n,表示石子堆数。
第二行一共n个正整数a[i],表示每堆的石子数。
输出描述:
一行两个整数,分别表示先手的胜率和所有可能的情况中先手获胜的次数,并对998244353取模。
sol:
1.考虑获胜的概率,如果每个人都身不由己,即n堆石子都为1,则获胜方是一定的,要么一定赢要么一定输,否则55开。
2.根据博弈游戏的性质,输赢只和游戏结束时的轮数有关,即先手奇数轮赢,偶数轮输。
3.由隔板法,一个大小为 m m m的堆, i i i次操作取完的方案数是 ( m − 1 i − 1 ) \binom{m-1}{i-1} (i−1m−1) 。
4.考虑dp方程,设 f [ i ] [ j ] f[i][j] f[i][j] 为前 i i i堆 j j j次取完的方案数。考虑可重集合的排列数,则有转移方程 f [ i ] [ j ] = ∑ k = 1 a [ i ] f [ i − 1 ] [ j − k ] ( a [ i ] − 1 k − 1 ) ( j k ) f[i][j] = \sum_{k=1}^{a[i]}f[i-1][j-k] \binom{a[i]-1}{k-1} \binom{j}{k} f[i][j]=k=1∑a[i]f[i−1][j−k](k−1a[i]−1)(kj)
看起来有点像个卷积形式,把组合数拆开改写一下就是
f
[
i
]
[
j
]
j
!
=
∑
b
+
k
=
j
f
[
i
−
1
]
[
b
]
(
a
[
i
]
−
1
k
−
1
)
1
b
!
1
k
!
\frac{f[i][j]}{j!} = \sum_{b+k =j} f[i-1][b] \binom{a[i]-1}{k-1} \frac{1}{b!} \frac{1}{k!}
j!f[i][j]=b+k=j∑f[i−1][b](k−1a[i]−1)b!1k!1
再移一下项就是
f
[
i
]
[
j
]
j
!
=
∑
b
+
k
=
j
f
[
i
−
1
]
[
b
]
b
!
(
a
[
i
−
1
]
k
−
1
)
1
k
!
\frac{f[i][j]}{j!} = \sum_{b+k = j} \frac{f[i-1][b]}{b!}\binom{a[i-1]}{k-1} \frac{1}{k!}
j!f[i][j]=b+k=j∑b!f[i−1][b](k−1a[i−1])k!1
喜闻乐见分治
N
T
T
NTT
NTT搞搞,然后发现之前的板子有个地方错了
code:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 50;
const int mod = 998244353;
const int inv2 = (mod + 1)>>1;
inline ll qpow(ll a, ll b){
ll ret;
for(ret=1;b;b>>=1,a = a*a%mod) if(b&1) ret = ret * a % mod;
return ret;
}
inline ll Inv(ll a, ll _mod){ return qpow(a, _mod - 2);}
ll fac[maxn], inv[maxn];
void init(int n){
fac[0] = fac[1] = inv[0] = inv[1] = 1;
for (int i = 2; i <= n; i++)
fac[i] = fac[i - 1] * i % mod;
inv[n] = Inv(fac[n], mod);
for (int i = n - 1; i > 1; i--)
inv[i] = inv[i + 1] * (i + 1) % mod;
}
inline ll C(int n, int m){
return fac[n] * inv[n - m] % mod * inv[m] % mod;
}
struct NTT {
int rev[maxn], dig[105];
int N, L;
int g;
void init_rev(int n){
//初始化原根
g = 3;
for (N = 1, L = 0; N <= n; N <<= 1, L++);
memset(dig, 0, sizeof(int) * (L + 1));
for (int i = 0; i < N; i++) {
rev[i] = 0;
int len = 0;
for (int t = i; t; t >>= 1)
dig[len++] = t & 1;
for (int j = 0; j < L; j++)
rev[i] = (rev[i] << 1) | dig[j];
}
}
void DFT(vector<ll>& a, int flag)
{
for (int i = 0; i < N; i++)
if (i < rev[i])
swap(a[i], a[rev[i]]);
for (int l = 1; l < N; l <<= 1) {
ll wn;
if (flag == 1)
wn = qpow(g, (mod - 1) / (2 * l));
else
wn = qpow(g, mod - 1 - (mod - 1) / (2 * l));
for (int k = 0; k < N; k += l * 2) {
ll w = 1;
ll x, y;
for (int j = k; j < k + l; j++) {
x = a[j];
y = a[j + l] * w % mod;
a[j] = (x + y) % mod;
a[j + l] = (x - y + mod) % mod;
w = w * wn % mod;
}
}
}
if (flag == -1) {
ll x = Inv(N, mod);
for (int i = 0; i < N; i++)
a[i] = a[i] * x % mod;
}
}
void mul(vector<ll>& a, vector<ll>& b, int m)
{
init_rev(m);
a.resize(N);
b.resize(N);
DFT(a, 1);
DFT(b, 1);
for (int i = 0; i < N; i++)
a[i] = a[i] * b[i] % mod;
DFT(a, -1);
int len = N - 1;
while (a[len] == 0) len--;
a.resize(len + 1);
}
} ntt;
vector<ll> v[maxn];
#define pii pair<int,int>
#define fi first
#define se second
#define MP make_pair
priority_queue<pii> Q;
int main(){
init(maxn - 1);
int n;
scanf("%d", &n);
bool flag = false;
for(int i = 1;i<=n;i++){
int a; scanf("%d",&a);
if(a>1) flag = true;
v[i].resize(a+1);
v[i][0] = 0;
for(int j = 1;j<=a;j++) v[i][j] = C(a-1,j-1) * inv[j] % mod;
Q.push(MP(-a,i));
}
while(Q.size()>1){
pii p1 = Q.top();
Q.pop();
pii p2 = Q.top();
Q.pop();
ntt.mul(v[p1.se],v[p2.se],v[p1.se].size() + v[p2.se].size());
Q.push(MP(-v[p1.se].size(),p1.se));
}
pii ret = Q.top();
ll ans = 0;
for(int i = 1;i<v[ret.se].size();i+=2)
(ans += v[ret.se][i] * fac[i] % mod) %= mod;
int pp = inv2;
if(!flag) pp = n&1;
printf("%d %lld\n",pp,ans);
return 0;
}