BZOJ4036-[HAOI2015]按位或-FMT

说在前面

me的天哪…me看这道题看了一晚上???
这道题主要还是用到了一个性质:变换的和等于和的变换
这样的话,一个很难求和的东西,如果它的变换很好求和(有性质的支撑),那么我们就可以先变换,求和,然后再变换回去
卧槽这么简单的道理,me特喵的为什么看了一晚上???哇啊啊啊啊啊想哭

UPD:噢对,关于如何理解FMT的那三行,可以去看看这个,里面有对FMT(算是吧?)的另一种解释:
liu_runda‘s 不会FWT的选手计算集合并卷积的方法(UPD:博主智商着急了)

UPD2:复习的时候看到这篇文章,突然反应过来,me之前写的都是啥……其实这个基础思路就是:卷积多次很麻烦,所以我们先正变换,然后快速幂。


题目

BZOJ4036传送门

题目大意

给出一个数字N,对于 0...2N1 0...2 N − 1 中的每个数字,给出这个数字被选择的概率,保证概率和为1
现在询问,期望选择多少次之后,所选的所有数字或起来为 2N1 2 N − 1

输入输出格式

输入格式:
第一行一个整数N,含义如题
接下来一行 2N 2 N 个实数,表示每个数字被选的概率

输出格式:
输出答案,误差不超过 106 10 − 6
如果永远都无法达成,输出INF


解法

我们把给出的概率数组记作 p[] p [ ] ,并把 p p 看作集合幂级数,定义乘法为集合或卷积

首先把答案表示出来:Ans就是 k=1k(pkpk1) ∑ k = 1 ∞ k ( p k − p k − 1 ) 中第 2N1 2 N − 1 项的系数。我们称呼右边这个式子为 f f

f拆开,发现是这样的: f=kpkpk1pk2p1p0 f = k p k − p k − 1 − p k − 2 ⋯ − p 1 − p 0 ,并不可算。(这里 p0 p 0 中,只有第0项为1,其他全0。因为 pk p k 的意义实际上是,选k次之后各个或集的概率。)

于是尝试一下变换,如果变换之后的计算方便,再变换回来就可以得到答案了

变换之前是集合或卷积,变换之后是对应下标的数乘。并且和的变换等于变换的和,于是
Mobius变换得到: f^=kp^kp^k1p^k2p^p^0 f ^ = k p ^ k − p ^ k − 1 − p ^ k − 2 ⋯ − p ^ − p ^ 0

对于其中下标为S的某一项,我们将之单独提出来看,即 f^s=kp^ksp^k1sp^k2sp^s1 f ^ s = k p ^ s k − p ^ s k − 1 − p ^ s k − 2 ⋯ − p ^ s − 1 ,除开第一项,剩下的是一个等比数列。那么分类讨论一下:

  • p^s p ^ s 不是1,那么第一项趋于0,我们可以直接忽略它,直接求等比数列得到 f^s=1p^s1 f ^ s = 1 p ^ s − 1
  • 是1,那么 f^s=k1k11k21110=0 f ^ s = k − 1 k − 1 − 1 k − 2 ⋯ − 1 1 − 1 0 = 0

然后我们就得出了 f^ f ^ ,也就是说,变换后的和是可以计算的,那么只需要逆变换回去,就能得到答案了

就做完了


下面是自带大常数的代码

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;

int ws , N ;
double f[(1<<20)+5] , eps = 1e-10 ;

int dcmp( double x ){
    if( x > -eps && x < eps ) return 0 ;
    return x > 0 ? 1 : -1 ;
}

int main(){
    scanf( "%d" , &ws ) , N = ( 1 << ws ) ;
    for( int i = 0 ; i < N ; i ++ )
        scanf( "%lf" , &f[i] ) ;
    for( int k = 0 ; k < 20 ; k ++ )
        for( int i = 0 ; i < N ; i ++ )
            if( i & ( 1 << k ) ) f[i] += f[ i^(1<<k) ] ;
    for( int i = 0 ; i < N ; i ++ ){
        if( !dcmp( f[i] - 1 ) ) f[i] = 0 ;
        else f[i] = 1.0 / ( f[i] - 1.0 ) ;
    }
    for( int k = 0 ; k < 20 ; k ++ )
        for( int i = 0 ; i < N ; i ++ )
            if( i & ( 1 << k ) ) f[i] -= f[ i^(1<<k) ] ;
    !dcmp( f[N-1] ) ? puts( "INF" ) : printf( "%f" , f[N-1] ) ;   
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值