競技プログラミング日記

主に AtCoder の記事です

AtCoder Beginner Contest 189F

ABC189F

漸化式を立てると,一見循環してしまう. これを式変形して循環しない形にして計算する.

振り出しに戻るものが厄介. そのときの答えを \(x\) とおいて, \(x\) に関する 1次式とみなす.
\(dp_{i} := i\) に居るときの答え(期待値) とする. \(x = dp_{0}\) とおく. \( dp_{i} \) たちを \(x\) の 1次式の状態で dp を計算する.

まず,振り出しに戻るマスが存在しない場合を考える. \(dp_{i} = \sum_{j \in [1,M]} dp_{i+j}/M + 1\). これは累積和で求まる. \(s := \sum_{j \in [1,M]} dp_{i+j}\) を保持しながら遷移すれば良い.

次に,振り出しに戻るマスを考える. \(dp_{0} = ax + b\) の形で表せるから, \(x = ax + b\) を得る. すなわち,\(x = b/(1-a)\) を得る. ただし,\(1-a = 0\) の場合に注意. \(b < 0\) の場合は起きない?

実装
\(x\) の \(1\)次式に対する \(+, -, /\) を定義する. これは struct を用いる. \(ax + b\) を object とみなしたいので, 代わりに係数 \(a,b\) を保持する.

使っている記号,マクロ等 "https://ecsmtlir.hatenablog.com/entry/2022/12/23/131925"

struct L{
  double a,b;
  // L(){}
  L(double a = 0, double b = 0) : a(a), b(b) {}

  L operator + (L x){ return L(a + x.a, b + x.b); }
  L operator - (L x){ return L(a - x.a, b - x.b); }
  L operator / (double x){ return L(a/x, b/x); }
};

int main() {
  ll n,m,k;
  cin >> n >> m >> k;
  vll va(n); rep(i,k) { ll p; cin >> p; va[p] = 1; }

  L s(0,0);
  vector<L> dp(n+1);
  drep(i,n){
    if(va[i]){
      dp[i] = L(1,0);
    }else{
      dp[i] = s/m + L(0,1);
    }
    s = s + dp[i];
    if(i+m < n+1) s = s - dp[i+m];
  }
  double a = dp[0].a, b = dp[0].b;
  if(a + 1e-6 > 1) {
    cout << -1 << endl;
    return 0;
  }
  L ans = b/(1-a);
  printf("%.10lf", ans.a) ;


  return 0;
}