【BZOJ4036】按位或(HAOI2015)-Min-Max容斥+FWT
测试地址:按位或
做法:本题需要用到Min-Max容斥+FWT。
因为一直是或,所以一个位置上如果有了,这个就会一直有下去,那么问题就变成了,每次选择一个子集,问所有点都被选过的期望次数。所有点都被选过的期望次数,也就相当于这些点中最后一个点被选的期望次数,容易想到Min-Max容斥:
那么现在问题就变成如何求了。注意到这个期望相当于,这些点中第一次被取到的期望次数,也就等于这些点被取到的概率的倒数。直接统计被取到的概率较难,考虑补集转化,变成求不被取到的概率,容易发现这就是所有的子集的概率和,记为。于是问题就变成快速求出所有集合的。
显然直接枚举计算是的,无法承受。这时我们看到标题里有一个FWT(这个引入太牵强了吧……),可是这里并没有出现位运算卷积之类的东西啊……
别被套进去了,谁说FWT只是拿来求位运算卷积的了?在关于或运算的FWT中,我们通过正变换可以得到。等等,仔细一想,这不就是求集合所有子集的和吗?于是我们用FWT来解决这个问题,时间复杂度为,而后面枚举集合就是的了,可以通过此题。
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
int m,n;
double p[1500010];
void FWT(double *a)
{
for(int mid=1;mid<n;mid<<=1)
for(int l=0;l<n;l+=(mid<<1))
for(int k=0;k<mid;k++)
{
double x=a[l+k],y=a[l+mid+k];
a[l+k]=x;
a[l+mid+k]=x+y;
}
}
int main()
{
scanf("%d",&m);
n=(1<<m);
for(int i=0;i<n;i++)
scanf("%lf",&p[i]);
FWT(p);
double ans=0.0;
for(int i=1;i<n;i++)
{
double cnt=-1.0;
int x=i;
while(x) x-=(x&(-x)),cnt=-cnt;
if (1.0-p[n-i-1]<1e-8) {printf("INF");return 0;}
ans+=cnt/(1.0-p[n-i-1]);
}
printf("%.10lf",ans);
return 0;
}