题解:CF1773G Game of Questions

hhhqx

2024-11-15 16:34:03

Solution

模拟赛中的题是求排列数,不过改为求排列数是纯诈骗,需要从求概率的角度来看这题。

先来考虑 O(3^m) 的做法:

考虑 O(m^2 2^m) 的做法。

Code:

#include <bits/stdc++.h>

using namespace std;
using LL = long long;

const int MAXM = (1ll << 20) + 3;

int n, m, pc[MAXM];
double c[MAXM], c1[MAXM], c2[MAXM]; // c 数组
double g[MAXM]; // g 数组
double f[MAXM], tmp[MAXM], _f[MAXM]; 

void ADD(double &x, double w){ x += w; }

void hdi_suf(double *A){
    for(int i = 0; i < m; i++){
        for(int s = 0; s < (1ll << m); s++) if((s >> i) & 1) ADD(A[s ^ (1ll << i)], A[s]);
    }
}
void _hdi_suf(double *A){ // 高维后缀差分
    for(int i = 0; i < m; i++){
        for(int s = 0; s < (1ll << m); s++) if((s >> i) & 1) ADD(A[s ^ (1ll << i)], - A[s]);
    }
}
void hdi_pre(double *A){
    for(int i = 0; i < m; i++){
        for(int s = 0; s < (1ll << m); s++) if((s >> i) % 2 == 0) ADD(A[s ^ (1ll << i)], A[s]);
    }
}

int main(){
  ios::sync_with_stdio(0), cin.tie(0);
    cin >> n >> m;
    int R = (1ll << m) - 1;
    for(int s = 0; s < (1ll << m); s++){
        for(int i = 0; i < m; i++) pc[s] += (s >> i) & 1;
    }
    for(int i = 1; i <= n; i++){
        int S = 0;
        for(int j = 0; j < m; j++){
            char ch; cin >> ch, S += (ch == '1') * (1ll << j);
        }
        g[S]++, c1[S]++, c2[S]++;
    }
    hdi_pre(c1), hdi_suf(c2);

  for(int s = 0; s < (1ll << m); s++){
        c[s] = (s == 0 ? 0 : n - c1[s ^ R] - c2[s]);
        if(c[s] < 0) return 1;
    }

    f[R] = 1;
    hdi_suf(g); // 先对 g 做一遍后缀和
    for(int i = m - 1; i >= 1; i--){
        for(int s = 0; s < (1ll << m); s++) tmp[s] = f[s], f[s] = (c[s] == 0 ? 0 : f[s] / c[s]);
        hdi_suf(f);
        for(int s = 0; s < (1ll << m); s++) _f[s] = f[s] * g[s];
        _hdi_suf(_f);

        for(int s = 0; s < (1ll << m); s++){
            if(pc[s] > i) f[s] = tmp[s];
            if(pc[s] == i) f[s] = _f[s];
            if(pc[s] < i) f[s] = 0;
        }
    }
    vector<double> ans(m, 0);
    for(int s = 1; s < (1ll << m); s++){
      if(c[s] != 0) continue; // 只统计最终状态的概率 
        for(int i = 0; i < m; i++) if((s >> i) & 1) ADD(ans[i], f[s]);
    }
  cout << fixed << setprecision(10) << ans[0] << "\n";
    return 0;
}