1. 程式人生 > >HAOI2015 按位或

HAOI2015 按位或

咕了很久的神仙題
很早就被安利來做了
但是一直沒有勇氣做,覺得太難了
今天終於肝了這道題.
其實——還是很難


題目連結

\(LOJ\)
\(Luogu\)


前置芝士:\(Min-Max\)容斥

這是我做的第一道\(Min-Max\)容斥題
好神仙的操作啊

\(Min-Max\)容斥(或者稱最值反演)具體而言是兩個公式——
\(Min(S)\)表示集合\(S\)中的最小值.
\(Max(S)\)表示集合\(S\)中的最大值.
\[ Max(S)=\sum_{T\subset S}(-1)^{|T|+1}Min(T)\\ Min(S)=\sum_{T\subset S}(-1)^{|T|+1}Max(T) \]

更神仙的是它支援期望,這在這道題中有很好的體現.


解析

我們用\(Max(S)\)表示集合\(S\)中最後的\(1\)出現時間的期望,用\(Min(S)\)表示集合S中最早的\(1\)出現時間的期望.
那麼,答案就是\(Max(2^n-1)\)
\(Min-Max\)容斥得\(Max(S)=\sum_{T\subset S}(-1)^{|T|+1}Min(T)\)
那麼我們只要求出所有的\(Min(S)\)即可
如何快速求\(Min(S)\)呢?
只要我們有一次\(rand\)到了\(T\),\(T\cap S\not=\emptyset\),那麼\(S\)中就有\(1\)了.
那麼\(Min(S)=\frac{1}{\sum_{T\cap S\not=\emptyset}P_T}\)


但是這個東西並不是那麼好求,直接列舉子集的複雜度是\(O(3^n)\)的,無法承受.
我們考慮容斥?(可能並不算容斥吧)
\(S'\)\(S\)在全集中的補集(即\(S'=S\oplus (2^n-1))\).
如果\(T\cap S=\emptyset\),那麼\(T\subset S'\).
因此\(\sum_{T\cap S\not=\emptyset}P_T=1-\sum_{T'\subset S'}P_{T'}\)
\(\sum_{T'\subset S'}P_{T'}\)是可以用\(FWT(FMT)\)求的.
因此我們就做一次\(FWT\)即可.
時間複雜度\(O(2^n*n)\)

程式碼如下
真的短得一匹

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#define N (21)
#define M (1<<N)
#define inf (0x7f7f7f7f)
#define rg register int
#define Label puts("NAIVE")
#define spa print(' ')
#define ent print('\n')
#define rand() (((rand())<<(15))^(rand()))
#define eps 1e-9
typedef long double ld;
typedef long long LL;
typedef unsigned long long ull;
using namespace std;
int n,Lim,pc[M]; ld a[M],ans;
int main(){
    scanf("%d",&n),Lim=(1<<n);
    for(int i=0;i<Lim;i++)scanf("%Lf",&a[i]);
    for(int i=1;i<Lim;i<<=1)
    for(int R=i<<1,j=0;j<Lim;j+=R)
    for(int k=j;k<j+i;k++)
    a[i+k]+=a[k];
    for(int i=0;i<Lim;i++)pc[i]=pc[i>>1]+(i&1);
    for(int i=1;i<Lim;i++){
        if(fabs(1.0-a[i^(Lim-1)])<eps){puts("INF"),exit(0);}
        ld t=1.0/(1.0-a[i^(Lim-1)]);if(pc[i]&1)ans+=t;else ans-=t;
    }
    printf("%.10Lf",ans);
}