重新学习组合计数
发现自己对关于多项式的一部分内容(比如下降幂多项式)一无所知,导致在计数题上经常失手,所以写了这么篇博客。
下降幂多项式:
定义:\(x^{\underline n}=\prod_0^{n-1}(x-i) = \frac{x!}{(x-n)!}\)
一个多项式可以和下降幂多项式唯一转换。
这在推公式时有一些用。
由于阶乘,下降幂多项式和egf,组合数有着巨大的联系。
它们通常能够互相转换。
所以在推式子时可能可以考虑这些转换。
\(x^{\underline n}\)的展开形式可以用倍增求。
下降幂多项式的点值:
构造关于下降幂点值的egf:\(\sum \frac{F(i)x^i}{i!}=\sum \frac{x^i}{i!}\sum _{j=0}^n\frac{i!}{(i-j)!}F_j\)
\(=\sum x^i\sum _{j=0}^n\frac{1}{(i-j)!}F_j\)
\(=\sum_{i=0}^n F_i\sum_{j=i}^{\inf} \frac{1}{(j-i)!}x^j=\sum_{i=0}^n F_i\sum_{j=0}^{\inf} \frac{1}{j!}x^j=\sum_{i=0}^n F_ix^ie^x=F(x)e^x\)
所以和\(e^x\)卷积就能得到点值的egf。
从点值得到egf:
\(EGF(F(x))=F(x)e^x\)
\(F(x)=EGF(F(x))e^{-x}\)
下降幂多项式乘法就是把点值求出来,然后对应位相乘,再换回下降幂。
注意点值某位\(x\)相乘后,要乘以\(x!\)。
普通多项式转下降幂多项式:就是带入连续点值,然后使用上面的方法转成下降幂。
时间复杂度\(O(n\log_2^2n)\)
下降幂多项式转普通多项式:
求出多项式的egf,然后通过这个可以求出多项式的点值。
然后快速插值即可。
下降幂多项式乘法代码如下:
signed main(){
jc[0]=ij[0]=1;
for(int i=1;i<N;i++)
jc[i]=jc[i-1]*i%mo;
ij[N-1]=qp(jc[N-1],mo-2);
for(int i=N-1;i;i--)
ij[i-1]=ij[i]*i%mo;
pl e,a,b,ii;
int n,m,le;
scanf("%lld%lld",&n,&m);
le=n+m+5;
e.resize(le);
a.resize(n+1);
b.resize(m+1);
for(int i=0;i<=n+m;i++)
e[i]=ij[i];
for(int i=0;i<=n;i++)
scanf("%lld",&a[i]);
for(int i=0;i<=m;i++)
scanf("%lld",&b[i]);
a=a*e;
b=b*e;
ii=iv(e);
for(int i=0;i<=le;i++)
a[i]=a[i]*b[i]%mo*jc[i]%mo;
a=a*ii;
for(int i=0;i<=n+m;i++)
printf("%lld ",a[i]);
}
组合计数中常见转化:
1.\(\sum i*a_i=\sum [a_i\geq j]\)
它的意义就是对于每个数\(x\),统计\(\geq x\)的方案数。
这样子可以把计数类问题转化为判定性问题。
2.拆幂贡献
把幂的每个元素拆进公式内。
3.只考虑式子的若干项
只考虑式子的若干项,可能更好做。
然后把原式向这个式子转化。
4.求导
很多题目都能求导。
5.利用等式的性质
小Q的序列:
地震后的幻想乡:
也是概率密度函数的学习笔记。
概率密度函数就是一个函数\(f(x)\),\(f(x)\)表示在\(x\)处取值的概率。
如果\(f(x)\)越大,在\(x\)处取值的概率就越大。
但是\(f(x)\)任意处的值为0。
使用如下公式描述函数在\([l,r]\)的取值可能性。
\(\int_l^rf(x)dx\)
概率密度函数必须满足:\(\int_{-\infty}^{+\infty}f(x)dx = 1\)
考虑一个sub-problem:求出原图上每条边为出现概率为\(x\),存在生成树的概率。
这是个经典dp问题。考虑正难则反,设\(f_s\)表示\(s\)集合内连通,\(g_s\)表示\(s\)集合不连通的概率。
根据最小数原理,每次枚举一个包含最小节点的集合\(t\)转移
\(f_s=1-f_tg_{s-t}*h(t,s-t)\)
其中\(h(t,s-t)\)表示\(t\)到\(s-t\)集合内无边的概率。
显然\(h\)能够在\(O(3^n)\)的时间内预处理。
回到原题。
用归纳法可以证明,\(f_{all}\)是一个关于\(x\)的最多\(m\)次多项式。
答案要我们求出mst最大边权不超过\(x\)的概率。
设\(g(x)\)表示mst最大边权不超过\(x\)的概率,则\(g'(x)\)表示mst最大边权的概率密度函数。
这是因为设\(h(x)\)表示mst最大边权的概率密度函数,则\(g=\int_0^ah(x)dx\)
显然\(g(0)=0\),这样子求导回来就是原函数。
于是把\(f\)设成一个多项式,然后使用\(f_s=1-f_tg_{s-t}*h(t,s-t)\)转移,最后求导即可。
\(\int_0^1xf_{all}'(x)\)就是答案。这是因为如果最大边的权值是\(x\),贡献就是\(x\)。
所以和\(x\)相乘,把多项式在区间\([0,1]\)积分就是答案。
但是\(h(t,s-t)\)也要设成一个多项式。
实现上的细节:发现多项式的系数非常大。
使用如下公式进行转化:
\(\frac{a}{b}=\lfloor\frac{a}{b}\rfloor + \frac{a\mod b}{b}\)
这样子的好处:前面的\(\lfloor\frac{a}{b}\rfloor\)是整数,我们可以忽略整数部分,然后加上小数部分。
当b较小但是系数较大时能提升精度。
最后用如下语句更新答案即可:
while(ans<0)
ans++;
while(ans>=1)
ans--;
#include<bits/stdc++.h>
using namespace std;
#define N 5010
#define int long long
int n,a[N],b[N],m;
struct pl{
__int128 a[100];
int l;
}f[N],pw[N],g,vt;
pl operator *(pl x,pl y){
pl rd;
memset(rd.a,0,sizeof(rd.a));
rd.l=x.l+y.l-1;
for(int i=0;i<x.l;i++)
for(int j=0;j<y.l;j++)
rd.a[i+j]+=x.a[i]*y.a[j];
return rd;
}
pl operator +(pl x,pl y){
pl rd;
memset(rd.a,0,sizeof(rd.a));
rd.l=max(x.l,y.l);
for(int i=0;i<rd.l;i++)
rd.a[i]=x.a[i]+y.a[i];
return rd;
}
pl operator -(pl x,pl y){
pl rd;
memset(rd.a,0,sizeof(rd.a));
rd.l=max(x.l,y.l);
for(int i=0;i<rd.l;i++)
rd.a[i]=x.a[i]-y.a[i];
return rd;
}
pl qd(pl x){
pl rd;
memset(rd.a,0,sizeof(rd.a));
rd.l=x.l-1;
for(int i=0;i<rd.l;i++)
rd.a[i]=x.a[i+1]*(i+1);
return rd;
}
int lb(int x){
return x&-x;
}
char st[N];
int tp;
void wr(__int128 x){
if(x<0){
putchar('-');
x=-x;
}
tp=0;
while(x){
st[++tp]=x%10;
x/=10;
}
for(int i=tp;i;i--)
putchar(st[i]+'0');
puts("");
}
signed main(){
scanf("%lld%lld",&n,&m);
for(int i=1;i<=m;i++){
scanf("%lld%lld",&a[i],&b[i]);
a[i]--;
b[i]--;
}
g.l=2;
g.a[0]=1;
g.a[1]=-1;
pw[0].l=1;
pw[0].a[0]=1;
for(int i=1;i<=m+2;i++)
pw[i]=pw[i-1]*g;
f[0].l=1;
f[0].a[0]=1;
for(int i=1;i<(1<<n);i++){
int ll=lb(i);
if(!(i-ll)){
f[i].l=1;
f[i].a[0]=1;
continue;
}
for(int j=i;j;j=(j-1)&i)
if(j&ll){
int va=0,p=i-j;
for(int k=1;k<=m;k++)
if((i&(1<<a[k]))&&((i&(1<<b[k]))))
va++;
for(int k=1;k<=m;k++){
if((p&(1<<a[k]))&&(p&(1<<b[k])))
va--;
else if((j&(1<<a[k]))&&(j&(1<<b[k])))
va--;
}
f[i]=f[i]+f[j]*pw[va];
}
f[i].l=max(f[i].l,1ll);
for(int j=0;j<f[i].l;j++)
f[i].a[j]=-f[i].a[j];
f[i].a[0]++;
}
long double ans=0;
f[(1<<n)-1]=qd(f[(1<<n)-1]);
vt.l=2;
vt.a[1]=1;
vt=vt*f[(1<<n)-1];
for(int i=0;i<vt.l;i++)
ans+=(vt.a[i]%(i+1))/((long double)i+1);
while(ans<0)
ans++;
while(ans>=1)
ans--;
printf("%.6Lf",ans);
}
上个问题改成求出生成树边权和。
考虑每条边的贡献。
由期望的线性性,答案由每条边对答案的贡献构成。
设\(f_i(x)\)表示mst上某边\(i\)在取值为\(x\)时在最小生成树的概率。
看上去一条边是否在mst上不好求,但是由于每条边的权值都是均匀实数,所以每条边的权值都是不同的。
考虑kruskal的过程,我们在之前插入了取值\(\leq x\)的所有边。
把取值\(\leq x\)的所有边插入图后判定边\(i\)两端点是否连通即可。
这样子就好做了。
可以用前文类似的dp计算答案,但是保证每个连通块不同时包含边\(i\)的两个端点。
时间复杂度\(O(3^mm^3)\)
jzoj5158