Official

C - Catastrophic Roulette Editorial by physics0523

解説

今までに出た整数の種類数を \(q\) とし、 \(q\) ごとに以下の量を管理することを考えましょう。

  • 出た整数の種類数が \(q\) になった直後の手番が 先攻 / 後攻 である確率 \(h_1,h_2\)
  • 出た整数の種類数が \(q\) になった直後の時点で、 先攻 / 後攻 が支払う罰金の期待値 \(f_1,f_2\)

\(q=1,2,\dots,N\) について順にこれらの量を更新していくことでこの問題に正解できます。

丁寧に立式して、これを実現しましょう。

\(q=1\) に対して、 \(h_1=0, h_2=1, f_1=0, f_2=0\) です。

以降、 \(\displaystyle p=\frac{q}{N}\) と置きます。

まず、 \(h\) の更新について着目します。
「出た整数の種類数が \(q\) になった直後の手番」「出た整数の種類数が \(q+1\) になった直後の手番」の二者が反転する確率は \(\displaystyle (1-p) + (1-p)p^2 + (1-p)p^4 + \dots\) と表現されます。

次に、 \(f\) の更新について着目します。
今手番を持っている側の罰金の増分の期待値は \(p + p^3 + p^5 + \dots\) 、持っていない側の罰金の増分の期待値は \(p^2 + p^4 + p^6 + \dots\) と表現されます。

一見無限和なので取り扱うことが難しいようにも見えますが、 \(0 < p < 1\) について \(\displaystyle 1 + p^2 + p^4 + p^6 + \dots = \sum_{i=0}^{\infty} p^{2i} = \frac{1}{1-p^2}\) が成り立つということを利用すると、ここまで登場した全ての無限和を処理することができます。

この計算を実装すると、この問題に時間計算量 \(O(N)\) で正解できます。

Bonus! この問題を有理数ジャッジにしてよい (すべての計算を分母が \(998244353\) の倍数でない有理数で完結させられる) 理由について考察してみましょう。

実装例(C++):

#include<bits/stdc++.h>

#define mod 998244353

long long power(long long a,long long b){
  long long x=1,y=a;
  while(b>0){
    if(b&1ll){
      x=(x*y)%mod;
    }
    y=(y*y)%mod;
    b>>=1;
  }
  return x%mod;
}

long long modular_inverse(long long n){
  return power(n,mod-2);
}

using namespace std;

int main(){
  ios::sync_with_stdio(false);
  cin.tie(nullptr);

  long long n;
  cin >> n;

  long long invn=modular_inverse(n);

  vector<long long> hand={0,1};
  vector<long long> fee={0,0};

  for(long long q=1;q<n;q++){
    long long prob=(q*invn)%mod;
    long long fce=modular_inverse(mod+1-((prob*prob)%mod));
    long long hce=((mod+1-prob)*fce)%mod;

    vector<long long> chand={
      (prob*hce)%mod,
      hce
    };
    vector<long long> cfee={
      (prob*fce)%mod,
      (((prob*prob)%mod)*fce)%mod,
    };

    vector<long long> nhand={0,0};

    for(long long i=0;i<2;i++){
      for(long long j=0;j<2;j++){
        nhand[(i+j)%2]+=hand[i]*chand[j];
        nhand[(i+j)%2]%=mod;

        fee[(i+j)%2]+=hand[i]*cfee[j];
        fee[(i+j)%2]%=mod;
      }
    }

    hand=nhand;
  }
  cout << fee[0]%mod << " " << fee[1]%mod << "\n";
  return 0;
}

posted:
last update: