1 前言
大概又是Min-Max容斥与FMT的裸题吧
刚学完Min-Max容斥,式子推的飞快
2 题目相关
2.1 题目大意
一开始你手上有一个数 0 0 0,每次或上随机一个在 [ 0 , 2 n ) [0,2^n) [0,2n)中的数(给出随到每个数的概率),求期望多少步你手上的数变为 2 n − 1 2^n-1 2n−1
2.2 数据范围
n ≤ 20 n\le 20 n≤20,所有概率的输入以及期望步数的输出都使用实数
3 题解
我们发现我们可以直接Min-Max容斥
m
a
x
{
S
}
=
∑
T
⊆
S
(
−
1
)
∣
T
∣
+
1
m
i
n
{
T
}
max\{S\}=\sum_{T\subseteq S}(-1)^{|T|+1}min\{T\}
max{S}=T⊆S∑(−1)∣T∣+1min{T}
我们发现
m
a
x
{
S
}
max\{S\}
max{S}相当于是变为
2
n
−
1
2^n-1
2n−1的期望步数,
m
i
n
{
T
}
min\{T\}
min{T}代表进行操作与集合
T
T
T有交的期望步数
我们就可以直接做了
我们知道,如果求出了
m
i
n
{
T
}
min\{T\}
min{T}我们可以直接
Θ
(
2
n
)
\Theta(2^n)
Θ(2n)计算答案了
考虑如何计算
m
i
n
{
T
}
min\{T\}
min{T}
设取到
S
S
S的概率为
g
S
g_S
gS,我们发现
m
i
n
{
S
}
=
1
1
−
∑
T
∩
S
=
∅
g
T
min\{S\}=\frac{1}{1-\sum_{T\cap S=\varnothing}g_T}
min{S}=1−∑T∩S=∅gT1
我们相当于要求
f
S
=
∑
T
∩
S
=
∅
g
T
f
S
=
∑
T
∩
(
C
u
S
)
=
T
g
T
f
S
=
∑
T
⊆
(
C
u
S
)
g
T
\begin{aligned} f_{S}&=\sum_{T\cap S=\varnothing}g_T\\ f_{S}&=\sum_{T\cap(Cu_S)=T}g_T\\ f_{S}&=\sum_{T\subseteq(Cu_S)}g_T\\ \end{aligned}
fSfSfS=T∩S=∅∑gT=T∩(CuS)=T∑gT=T⊆(CuS)∑gT
然后翻转一下,就是子集卷积了,使用FMT即可
非常方便
4 代码
贴上我写的第一份代码(未卡常版本)
#include<cstdio>
#include<cctype>
#include<algorithm>
namespace fast_IO
{
const int IN_LEN=10000000,OUT_LEN=10000000;
char ibuf[IN_LEN],obuf[OUT_LEN],*ih=ibuf+IN_LEN,*oh=obuf,*lastin=ibuf+IN_LEN,*lastout=obuf+OUT_LEN-1;
inline char getchar_(){return (ih==lastin)&&(lastin=(ih=ibuf)+fread(ibuf,1,IN_LEN,stdin),ih==lastin)?EOF:*ih++;}
inline void putchar_(const char x){if(oh==lastout)fwrite(obuf,1,oh-obuf,stdout),oh=obuf;*oh++=x;}
inline void flush(){fwrite(obuf,1,oh-obuf,stdout);}
}
using namespace fast_IO;
#define getchar() getchar_()
#define putchar(x) putchar_((x))
#define rg register
typedef long long LL;
template <typename T> inline T max(const T a,const T b){return a>b?a:b;}
template <typename T> inline T min(const T a,const T b){return a<b?a:b;}
template <typename T> inline void mind(T&a,const T b){a=a<b?a:b;}
template <typename T> inline void maxd(T&a,const T b){a=a>b?a:b;}
template <typename T> inline T abs(const T a){return a>0?a:-a;}
template <typename T> inline void swap(T&a,T&b){T c=a;a=b;b=c;}
template <typename T> inline T gcd(const T a,const T b){if(!b)return a;return gcd(b,a%b);}
template <typename T> inline T lcm(const T a,const T b){return a/gcd(a,b)*b;}
template <typename T> inline T square(const T x){return x*x;};
template <typename T> inline void read(T&x)
{
char cu=getchar();x=0;bool fla=0;
while(!isdigit(cu)){if(cu=='-')fla=1;cu=getchar();}
while(isdigit(cu))x=x*10+cu-'0',cu=getchar();
if(fla)x=-x;
}
double xs[11][10];
template <typename T> inline void Read(T&x)
{
char cu=getchar();x=0;bool fla=0;
while(!isdigit(cu)){if(cu=='-')fla=1;cu=getchar();}
while(isdigit(cu))x=x*10+cu-'0',cu=getchar();
if(cu=='.')
{
cu=getchar();int t=0;
while(isdigit(cu))x+=xs[++t][cu-'0'],cu=getchar();
}
if(fla)x=-x;
}
template <typename T> inline void printe(const T x)
{
if(x>=10)printe(x/10);
putchar(x%10+'0');
}
template <typename T> inline void print(const T x)
{
if(x<0)putchar('-'),printe(-x);
else printe(x);
}
int bit[21],n,lenth;
double g[2097152],ans;
bool pc[2097152];
int main()
{
xs[0][1]=1;
for(rg int i=1;i<=10;i++)
{
xs[i][1]=xs[i-1][1]/10;
for(rg int j=1;j<=9;j++)xs[i][j]=xs[i][1]*j;
}
bit[0]=1;
for(rg int i=1;i<=20;i++)bit[i]=bit[i-1]<<1;
read(n),lenth=bit[n];
for(rg int i=lenth-1;i>=0;i--)Read(g[i]);
for(rg int i=1;i<lenth;i<<=1)
for(rg int j=0;j<lenth;j+=(i<<1))
for(rg int k=0;k<i;k++)
g[j+k]+=g[j+k+i];
const int Low=lenth-1;
for(rg int i=1;i<lenth;i++)
{
pc[i]=pc[i>>1]^(i&1);
if(g[i]>=0.999999999)
{
puts("INF");
return flush(),0;
}
if(pc[i])ans+=1.0/(1-g[i]);
else ans-=1.0/(1-g[i]);
}
printf("%.8lf",ans);
return flush(),0;
}
5 总结
这道题我进行了一波卡常(你看我写的第一份代码已经用实数读优了)
然后我学习到了一个小技巧
这题里的FMT我用的是FWTand的写法(写FWTor也可以,反正不是两重循环的)
然后对于FWT,将最底下的两层单独提出来用for循环做,速度会有明显的提升
就不贴卡常后的代码了