快速莫比乌斯变换
关于集合幂级数的更多内容请看2015年集训队论文最后一篇VFK的。orz orz
主要思路就是把求一个集合幂级数的卷积转化成一个求子集和的问题。过程是作出莫比乌斯变换,变成求和,再快速莫比乌斯反演回去。
记
fi(s)
为
i
次过后,并集为
那我们要求的就是
h(U)=∑∞i=1i∗[fi(U)−fi−1(U)]
对两边做莫比乌斯变换
h^(U)=∑∞i=1i∗[f^i(U)−f^i−1(U)]
我们希望快速求出右边,从而反演出左边,观察 f^i(U) 的定义式
f^i(U)=∑s1∈Ufi−1(s1)∗∑s2∈Uf1(s2)
整合一下,有
f^i(U)=[f^1i(U)]i
因此,有
h^(U)=∑∞i=1i∗[f^1(U)]i−[f^1(U)]i−1]
右边的和式可以通过扰动法求出等比数列的和,显然是收敛的,即
h^(U)=1f^1(U)−1
把 h^(U) 反演回 h(U) 即可得到答案
h(U)=∑s∈U1f^1(s)−1∗(−1)U中1的个数−s中1的个数
一个细节是上述的等比数列求和时,若
f1(U)=1
时不能用直那样求和,否则会除以
0
. 正确做法是忽略它,因为它的和总是
#include<cstdio>
#define N 21
using namespace std;
namespace runzhe2000
{
int n, g[1<<N]; double f[1<<N], h;
void main()
{
scanf("%d",&n);
int U = (1<<n)-1, V;
g[0] = 1;
for(int i = 0; i <= U; i++)
{
scanf("%lf",&f[i]);
i?g[i] = -g[i-(i&-i)]:0;
f[i]?V|=i:0;
}
if(V!=U){puts("INF");return;}
for(int i = 0; i < n; i++)
for(int j = 1; j <= U; j++)
(j&(1<<i)) ? f[j] += f[j-(1<<i)] : 0;
for(int i = 0; i < U; i++) h += g[i] / (f[i] - 1);
printf("%.8lf\n",h);
}
}
int main()
{
runzhe2000::main();
}