参考大佬博客:https://www.cnblogs.com/utopia999/p/9898393.html
基本就这俩式子:
max(S)=
∑
T
⊆
S
(
−
1
)
∣
T
∣
−
1
∗
m
i
n
(
T
)
\sum_{T\subseteq S} (-1)^{|T|-1}*min(T)
∑T⊆S(−1)∣T∣−1∗min(T)
min的话上面min和max反一反…
还有一个推广的式子
kth-max(S)=
∑
T
⊆
S
(
−
1
)
∣
T
∣
−
k
∗
(
∣
T
∣
−
1
k
−
1
)
∗
m
i
n
(
T
)
\sum_{T\subseteq S}(-1)^{|T|-k}*\binom {|T|-1} {k-1}*min(T)
∑T⊆S(−1)∣T∣−k∗(k−1∣T∣−1)∗min(T)
看上去很没有用,但其实它在算期望(或者lcm)的时候还是很厉害的。
lcm:
期望:
(上面这些均转载自开头大佬博客)
证明用到二项式反演,参见这名大佬博客:
https://blog.csdn.net/ez_2016gdgzoi471/article/details/81416333
例题:
T1:hdu4336 Card Collector
设max(s)为当前集合最后一个元素第一次取到的期望时间,min(s)为当前集合第一个元素第一次取到的时间,感性理解一下就是max和min的关系,而min-max容斥在期望意义下是成立的,所以直接上容斥即可,min(s)就是s中元素概率和的倒数。
代码:
#include<bits/stdc++.h>
#define db double
using namespace std;
const int N=25;
int n;
db a[N],ans=0;
void dfs(int pos,int cnt,db res)
{
if(pos>n)
{
if(cnt==0)return;
ans+=(cnt&1)?1.0/res:(-1.0)/res;
return;
}
dfs(pos+1,cnt+1,res+a[pos]);
dfs(pos+1,cnt,res);
}
int main()
{
while(~scanf("%d",&n))
{
ans=0;
for(int i=1;i<=n;i++)
scanf("%lf",&a[i]);
dfs(1,0,0);
printf("%.6lf\n",ans);
}
}
T2:bzoj4036 按位或
基本与之前那题相同,设max(s)为期望s中最后一个1最早出现时间,min(s)为s中第一个1最早出现时间就能上min-max容斥,不过这题的min稍微难算一点,min(s)是所有与s有交集的集合的概率和,那么就先取反变成sum减去是取反后的数的子集和,fwt就行了。
#include<bits/stdc++.h>
#define db double
using namespace std;
const int N=2e6+10;
int n,lim,cnt[N];
db a[N],sum=0;
void fwt(db *a)
{
for(int l=2,md=1;l<=n;l<<=1,md<<=1)
for(int i=0;i<n;i+=l)
for(int j=0;j<md;j++)
a[i+j+md]+=a[i+j];
}
int main()
{
db ans=0,nw;
scanf("%d",&n),n=1<<n,lim=n-1;
for(int i=0;i<n;i++)
scanf("%lf",&a[i]),sum+=a[i];
cnt[1]=1;
for(int i=2;i<n;i++)
cnt[i]=cnt[i>>1]+(i&1);
fwt(a);
for(int i=1;i<n;i<<=1)
{
nw=sum-a[lim^i];
if(nw==0)return puts("INF"),0;
}
for(int i=1;i<n;i++)
nw=sum-a[lim^i],ans+=(cnt[i]&1)?1.0/nw:(-1.0)/nw;
printf("%.8lf\n",ans);
}
T3:洛谷P4707重返现世
https://www.luogu.org/problemnew/show/P4707
根本不会.jpg
首先要用到min-max容斥的扩展式:
kth-max(S)=
∑
T
⊆
S
(
−
1
)
∣
T
∣
−
k
∗
(
∣
T
∣
−
1
k
−
1
)
∗
m
i
n
(
T
)
\sum_{T\subseteq S}(-1)^{|T|-k}*\binom {|T|-1} {k-1}*min(T)
∑T⊆S(−1)∣T∣−k∗(k−1∣T∣−1)∗min(T)
其中的kth-max表示S集合里第k大的元素第一次出现的期望时间,min(S)表示S集合里最早出现的元素第一次出现的期望时间,min(S)=
m
/
∑
i
⊂
S
p
(
i
)
m/\sum_{i\subset S}p(i)
m/∑i⊂Sp(i)
p表示出现i的概率
但这题n=1000,2^n就不用想了,所以考虑dp:
dp(k,i,j)表示当前算的是kth-max,考虑到了第i个物品,当前集合所有元素的p之和为j的集合的
∑
(
−
1
)
∣
T
∣
−
k
∗
(
∣
T
∣
−
1
k
−
1
)
\sum(-1)^{|T|-k}*\binom {|T|-1} {k-1}
∑(−1)∣T∣−k∗(k−1∣T∣−1)
那么最后的答案就是kth-max(S)=
∑
1
<
=
j
<
=
m
d
p
(
K
,
n
,
j
)
∗
(
m
/
j
)
\sum_{1<=j<=m}dp(K,n,j)*(m/j)
∑1<=j<=mdp(K,n,j)∗(m/j)
K是题目里读入的K。
而最终只要算题目给定的K,为什么还要开k这一维?是用于转移的:
考虑dp(k,i,j)是如何转移过来的:
第一种情况很简单,i这个位置上的元素没有放入集合,那么得到:
dp(k,i,j)+=dp(k,i-1,j)
第二种情况就难了,i这个位置上的元素放入了集合,那么它会导致
∑
(
−
1
)
∣
T
∣
−
k
∗
(
∣
T
∣
−
1
k
−
1
)
\sum(-1)^{|T|-k}*\binom {|T|-1} {k-1}
∑(−1)∣T∣−k∗(k−1∣T∣−1)这个式子发生变化,|T|变大1,对前半部分的贡献就是*(-1),而后半部分有关组合数贡献怎么算呢?考虑组合数的递推式
(
n
m
)
=
(
n
−
1
m
)
+
(
n
−
1
m
−
1
)
\binom n m=\binom {n-1} {m} + \binom {n-1} {m-1}
(mn)=(mn−1)+(m−1n−1)那么有
(
∣
T
∣
−
1
k
−
1
)
=
(
∣
T
∣
−
2
k
−
1
)
+
(
∣
T
∣
−
2
k
−
2
)
\binom {|T|-1} {k-1}=\binom {|T|-2} {k-1} + \binom{|T|-2}{k-2}
(k−1∣T∣−1)=(k−1∣T∣−2)+(k−2∣T∣−2)
所以dp(k,i,j)+=(-1)*dp(k,i-1,j-p(i))+dp(k-1,i-1,j-p(i))
还有一个东西就是dp的初值,当T这个集合为空集的时候,发现坑爹的事情出现了,要算一个-1取多少的组合数,这东西肯定没法搞,所以考虑一下后面基于它的状态的情况来反推它的值,而当T这个集合元素个数为1且k=1的时候,之前0个元素的集合对它要产生1的贡献,而根据推得的贡献式子0个元素产生的贡献应该是dp(0,i-1,0)-dp(1,i-1,0),那么把dp(0,i,0)都赋成1即可(网上大部分题解里写的把dp(K,0,0)赋成-1个人太菜不太理解…)
数组开不下把i那维滚动即可
复杂度nmk,注意这里的k是n-k+1,因为题目要的是第k小,转化过来是第n-k+1大,所以复杂度是对的。
刚开始3维数组被卡常了…
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=998244353;
const int N=1050,M=1e4+10;
int n,K,m,p[N],f[2][12][M],inv[M];
void Ad(int &x,int y)
{if((x+=y)>=mod)x-=mod;}
void Dw(int &x,int y)
{if((x-=y)<0)x+=mod;}
int main()
{
int ans=0,nw,bf;
scanf("%d%d%d",&n,&K,&m),K=n-K+1;
for(int i=1;i<=n;i++)
scanf("%d",&p[i]);
for(int i=1;i<=n;i++)
{
nw=i&1,bf=nw^1;
memset(f[nw],0,sizeof f[nw]);
f[bf][0][0]=1;
for(int k=1;k<=K;k++)
{
for(int j=0;j<=m;j++)
{
Ad(f[nw][k][j],f[bf][k][j]);
if(j>=p[i])Ad(f[nw][k][j],f[bf][k-1][j-p[i]]),Dw(f[nw][k][j],f[bf][k][j-p[i]]);
}
}
}
inv[1]=1;
for(int j=2;j<=m;j++)
inv[j]=1LL*(mod-mod/j)*inv[mod%j]%mod;
for(int j=1;j<=m;j++)
Ad(ans,1LL*f[nw][K][j]*m%mod*inv[j]%mod);
printf("%d\n",ans);
}
直接压掉第一维就过了…
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=998244353;
const int N=1050,M=1e4+10;
int n,K,m,p[N],f[12][M],inv[M];
void Ad(int &x,int y)
{if((x+=y)>=mod)x-=mod;}
void Dw(int &x,int y)
{if((x-=y)<0)x+=mod;}
int main()
{
int ans=0;
scanf("%d%d%d",&n,&K,&m),K=n-K+1;
for(int i=1;i<=n;i++)
scanf("%d",&p[i]);
for(int i=1;i<=n;i++)
{
f[0][0]=1;
for(int k=K;k>=1;k--)
{
for(int j=m;j>=0;j--)
{
if(j>=p[i])Ad(f[k][j],f[k-1][j-p[i]]),Dw(f[k][j],f[k][j-p[i]]);
}
}
}
inv[1]=1;
for(int j=2;j<=m;j++)
inv[j]=1LL*(mod-mod/j)*inv[mod%j]%mod;
for(int j=1;j<=m;j++)
Ad(ans,1LL*f[K][j]*m%mod*inv[j]%mod);
printf("%d\n",ans);
}
T4:「PKUWC2018」随机游走
链接:https://loj.ac/problem/2542
期望啥的忘光了…首先肯定要用min-max容斥,那么所求就是一个集合最早到达点的第一次到达期望时间,n范围很小,考虑暴力枚举集合,那么可以设一个f(x)表示从x号点最早走到集合内点期望时间,得到如下转移
f
(
x
)
=
0
       
当
x
⊂
S
时
f(x)=0 \,\,\,\,\,\,\, 当x\subset S时
f(x)=0当x⊂S时
f
(
x
)
=
1
d
x
(
1
+
f
(
y
)
+
∑
v
⊂
s
o
n
(
x
)
(
f
(
v
)
+
1
)
)
f(x)=\frac{1}{dx}(1+f(y)+\sum_{v\subset son(x)}(f(v)+1))
f(x)=dx1(1+f(y)+∑v⊂son(x)(f(v)+1))
其中y表示x点的父亲,dx表示x的度数
这样发现一个点f(x)可以表示成多少f(y)+常数,子节点传到父节点后可以列方程同理把父节点转化成这样的形式,而根节点是没有父亲的,所以从下到上转移最后到根了就不会有多少f(y),变成一个只有常数的东西,就是我们要求的当前集合S的答案,访问到S集合内的点把两个东西都赋成0即可。
最后求得了min,跑一个fwt就能得到答案了。
复杂度2 ^ n * n * log (log是树形dp时候的逆元)
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=20,mod=998244353;
int n,Q,lim,rt,d[N],k[N],b[N],ans[1<<18],cnt[1<<18];
bool inS[N];
vector<int>mp[N];
void Ad(int &x,int y)
{if((x+=y)>=mod)x-=mod;}
void Dw(int &x,int y)
{if((x-=y)<0)x+=mod;}
void Mul(int &x,int y)
{x=1LL*x*y%mod;}
int qpow(int x,int y)
{
int res=1;
while(y)
{
if(y&1)res=1LL*res*x%mod;
x=1LL*x*x%mod,y>>=1;
}
return res;
}
int inv(int x)
{return qpow(x,mod-2);}
void dfs(int x,int y)
{
if(inS[x]){k[x]=b[x]=0;return;}
int tp=1,mo=inv(d[x]);
b[x]=1,k[x]=(x==rt?0:mo);
for(int i=0,v;i<mp[x].size();i++)
{
v=mp[x][i];
if(v==y)continue;
dfs(v,x),Dw(tp,1LL*k[v]*mo%mod),Ad(b[x],1LL*b[v]*mo%mod);
}
tp=inv(tp);
Mul(k[x],tp),Mul(b[x],tp);
}
int fwt(int *a)
{
for(int l=2,md=1;l<=lim;l<<=1,md<<=1)
for(int i=0;i<lim;i+=l)
for(int j=0;j<md;j++)
Ad(a[i+j+md],a[i+j]);
}
int main()
{
int u,v;
scanf("%d%d%d",&n,&Q,&rt),lim=1<<n;
for(int i=1;i<n;i++)
{
scanf("%d%d",&u,&v);
mp[u].push_back(v);
mp[v].push_back(u);
}
for(int i=1;i<=n;i++)d[i]=mp[i].size();
for(int i=1;i<lim;i++)
{
for(int j=1;j<=n;j++)
{
if((1<<(j-1))&i)inS[j]=1;
else inS[j]=0;
}
dfs(rt,0),ans[i]=b[rt];
//cerr<<i<<' '<<ans[i]<<'\n';
}
cnt[1]=1;
for(int i=2;i<lim;i++)
cnt[i]=cnt[i>>1]+(i&1);
for(int i=1;i<lim;i++)
if(cnt[i]%2==0)Mul(ans[i],mod-1);
fwt(ans);
int cc,res,tp;
for(int i=1;i<=Q;i++)
{
res=0;
scanf("%d",&cc);
for(int j=1;j<=cc;j++)
scanf("%d",&tp),res|=(1<<(tp-1));
printf("%d\n",ans[res]);
}
}