Wannafly 11 挑战赛F 白兔的游戏

5 篇文章 0 订阅
5 篇文章 0 订阅

题目:

链接: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 bxa mod 998244353 0 ≤ x &lt; 998244353 0 ≤ x &lt; 998244353 0x<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} (i1m1)

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=1a[i]f[i1][jk](k1a[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=jf[i1][b](k1a[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=jb!f[i1][b](k1a[i1])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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值