洛谷P3175 [HAOI2015]按位或 (min-max容斥 + 高维前缀和)
题目链接:https://www.luogu.com.cn/problem/P3175
\(min-max\) 容斥公式 : $$max(S) = \sum\limits_{T!=\phi, T\subseteq S}(-1)^{T-1}min(T)$$
\[min(S) = \sum\limits_{T!=\phi, T\subseteq S}(-1)^{T-1}max(T)
\]
公式在期望意义下也成立,当我们无法比较集合中元素的大小关系时,可以通过 \(min-max\) 容斥求出最大值或最小值
将每个位置看成一个元素,设 \(max(S)\) 表示集合中最后一个元素被或为 \(1\) 的时间,最后一个元素被或为 \(1\),也即所有元素都被或为 \(1\),\(min(T)\) 表示集合中第一个元素被或为 \(1\) 的时间,根据定义,由无穷级数求和,概率为 $$\frac{1}{\sum\limits_{X\cap T != \phi}p(X)}$$
简单容斥一下,变为 $$\frac{1}{1-\sum\limits_{X\cap T = \phi}p(X)}$$
后者可以通过计算集合 \(T\) 的子集和求出,高维前缀和即可(fwt也可以)
注意判断无解的情况,当分子趋向于 \(0\),即永远有元素无法被或为 \(1\) 时无解
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 200010;
const int maxm = (1 << 20) + 10;
const double eps = 1e-8;
int n, m, N; ll k;
double p[maxm];
char s[maxn];
int count(int x){
int cnt = 0;
while(x){
if(x&1) ++cnt;
x >>= 1;
}
return cnt;
}
ll read(){ ll s = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){ if(ch == '-') f = -1; ch = getchar(); } while(ch >= '0' && ch <= '9'){ s = s * 10 + ch - '0'; ch = getchar(); } return s * f; }
int main(){
scanf("%d", &n);
for(int i = 0 ; i < (1 << n) ; ++i) scanf("%lf", &p[i]);
for(int j = 0 ; j < n ; ++j){
for(int i = 0 ; i < (1 << n) ; ++i){
if((i>>j)&1) p[i] = p[i] + p[i ^ (1<<j)];
}
}
int all = (1 << n) - 1;
double ans = 0;
for(int i = 1 ; i < (1 << n) ; ++i){
int cnt = count(i);
if(fabs(1.0-p[all^i] < eps)) {
printf("INF\n"); return 0;
}
int f = (cnt & 1) ? 1 : -1;
ans += 1.0 * f * (1.0 / (1.0-p[all^i]));
}
printf("%.6lf\n", ans);
return 0;
}