min-max容斥
给定集合
S
S
S,设
m
a
x
(
S
)
max(S)
max(S)为
S
S
S中的最大值,
m
i
n
(
S
)
min(S)
min(S)为
S
S
S中的最小值,则我们有一个式子:
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)
这个东西就叫做min-max容斥。
怎么证明?
我们考虑构造一个容斥系数
f
(
x
)
f(x)
f(x),使得
m
a
x
(
S
)
=
∑
T
⊆
S
f
(
∣
T
∣
)
m
i
n
(
T
)
max(S)=\sum_{T\subseteq S}f(|T|)min(T)
max(S)=T⊆S∑f(∣T∣)min(T)
考虑第
x
+
1
x+1
x+1大的元素会被统计到的贡献。
则这个贡献为
∑
i
=
0
x
C
x
i
×
f
(
i
+
1
)
\sum_{i=0}^{x}C_{x}^{i}\times f(i+1)
∑i=0xCxi×f(i+1)
简单来说,就是枚举有哪些集合的最小值会为第
x
+
1
x+1
x+1大的元素。
则
[
x
=
=
0
]
=
∑
i
=
0
x
C
x
i
×
f
(
i
+
1
)
[x==0]=\sum_{i=0}^{x}C_{x}^{i}\times f(i+1)
[x==0]=i=0∑xCxi×f(i+1)
二项式反演一下
f
(
x
+
1
)
=
∑
i
=
0
x
(
−
1
)
x
−
i
C
x
i
[
i
=
=
0
]
f(x+1)=\sum_{i=0}^{x}(-1)^{x-i}C_{x}^{i}[i==0]
f(x+1)=i=0∑x(−1)x−iCxi[i==0]
得到
f
(
x
+
1
)
=
(
−
1
)
x
f(x+1)=(-1)^x
f(x+1)=(−1)x
因此
f
(
x
)
=
(
−
1
)
x
−
1
f(x)=(-1)^{x-1}
f(x)=(−1)x−1
综上,
m
a
x
(
S
)
=
∑
T
⊆
S
f
(
∣
T
∣
)
m
i
n
(
T
)
=
∑
T
⊆
S
(
−
1
)
∣
T
∣
−
1
m
i
n
(
T
)
max(S)=\sum_{T\subseteq S}f(|T|)min(T)\\=\sum_{T\subseteq S}(-1)^{|T|-1}min(T)
max(S)=T⊆S∑f(∣T∣)min(T)=T⊆S∑(−1)∣T∣−1min(T)
证明完毕。
下面我们来看几道例题。
由于min-max容斥对于期望同样满足,所以可以很方便地解决一些概率和期望问题。
hdu4336 Card Collector
有n种卡片,每一秒都有
P
i
P_i
Pi的概率获得一张第
i
i
i种卡片,求每张卡片都至少有一张的期望时间。
我们记
m
a
x
(
S
)
max(S)
max(S)为
S
S
S中最后获得的那种卡片第一次获得的期望时间,
m
i
n
(
S
)
min(S)
min(S)为
S
S
S中的第一个获得的那种卡片第一次获得的期望时间。
仍然满足
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
i
n
(
T
)
=
1
∑
i
∈
T
P
i
min(T)=\frac{1}{\sum_{i\in T} P_i}
min(T)=∑i∈TPi1
直接计算即可。
代码
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=25;
const double eps=1e-7;
double a[N],ans;
int n;
void dfs(int now,int tot,double sum){
if(now>n){
if(sum>eps){
if(tot&1){
ans+=1/sum;
}else{
ans-=1/sum;
}
}
return;
}
dfs(now+1,tot,sum);
dfs(now+1,tot+1,sum+a[now]);
}
int main(){
while(~scanf("%d",&n)){
for(int i=1;i<=n;i++){
scanf("%lf",&a[i]);
}
ans=0;
dfs(1,0,0);
printf("%lf\n",ans);
}
return 0;
}
bzoj4036 按位或
我们记
m
a
x
(
S
)
max(S)
max(S)为
S
S
S中最后被或到的元素第一次被或到的期望时间,
m
i
n
(
S
)
min(S)
min(S)为
S
S
S中的第一个被或到的元素第一次被或到的期望时间。
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
i
n
(
S
)
=
1
∑
T
∩
S
≠
∅
P
T
min(S)=\frac{1}{\sum_{T\cap S≠\emptyset}P_T}
min(S)=∑T∩S=∅PT1
我们如果求出了
m
i
n
(
s
)
min(s)
min(s),就直接计算就好了。
怎么求?
∑
T
∩
S
≠
∅
P
T
=
s
u
m
−
∑
T
∩
S
=
∅
P
T
=
s
u
m
−
∑
T
∩
(
a
l
l
−
S
)
=
(
a
l
l
−
S
)
P
T
\sum_{T\cap S≠\emptyset}P_T\\=sum-\sum_{T\cap S=\emptyset}P_T\\=sum-\sum_{T\cap (all-S)=(all-S)}P_T
T∩S=∅∑PT=sum−T∩S=∅∑PT=sum−T∩(all−S)=(all−S)∑PT
然后
F
W
T
FWT
FWT计算即可。
代码
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=1050005;
int n,all,tmp,cnt[N];
double sum,res,ans,a[N];
int main(){
scanf("%d",&n);
all=1<<n;
for(int i=0;i<all;i++){
cnt[i]=cnt[i>>1]+(i&1);
scanf("%lf",&a[i]);
sum+=a[i];
if(fabs(a[i])>1e-6){
tmp|=i;
}
}
if(tmp!=all-1){
puts("INF");
return 0;
}
for(int i=1;i<all;i<<=1){
for(register int j=0;j<all;j+=(i<<1)){
for(register int k=j;k<j+i;k++){
a[k+i]+=a[k];
}
}
}
for(register int i=0;i<all;i++){
res=sum-a[all-1-i];
if(res>1e-6){
res=1/res;
}else{
res=0;
}
if(cnt[i]&1){
ans+=res;
}else{
ans-=res;
}
}
printf("%.8lf\n",ans);
return 0;
}
推广 kth min-max容斥
我们考虑构造一个容斥系数
f
(
x
)
f(x)
f(x),使得
k
t
h
m
a
x
(
S
)
=
∑
T
⊆
S
f
(
∣
T
∣
)
m
i
n
(
T
)
kthmax(S)=\sum_{T\subseteq S}f(|T|)min(T)
kthmax(S)=T⊆S∑f(∣T∣)min(T)
考虑第
x
+
1
x+1
x+1大的元素会被统计到的贡献。
这个贡献为
∑
i
=
0
x
C
x
i
×
f
(
i
+
1
)
\sum_{i=0}^{x}C_{x}^{i}\times f(i+1)
∑i=0xCxi×f(i+1)
则
[
x
=
=
k
−
1
]
=
∑
i
=
0
x
C
x
i
×
f
(
i
+
1
)
[x==k-1]=\sum_{i=0}^{x}C_{x}^{i}\times f(i+1)
[x==k−1]=i=0∑xCxi×f(i+1)
二项式反演一下
f
(
x
+
1
)
=
∑
i
=
0
x
(
−
1
)
x
−
i
C
x
i
[
i
=
=
k
−
1
]
f(x+1)=\sum_{i=0}^{x}(-1)^{x-i}C_{x}^{i}[i==k-1]
f(x+1)=i=0∑x(−1)x−iCxi[i==k−1]
得到
f
(
x
+
1
)
=
(
−
1
)
(
x
−
(
k
−
1
)
)
C
x
k
−
1
f(x+1)=(-1)^{(x-(k-1))}C_{x}^{k-1}
f(x+1)=(−1)(x−(k−1))Cxk−1
因此
f
(
x
)
=
(
−
1
)
x
−
k
C
x
−
1
k
−
1
f(x)=(-1)^{x-k}C_{x-1}^{k-1}
f(x)=(−1)x−kCx−1k−1
综上,
k
t
h
m
a
x
(
S
)
=
∑
T
⊆
S
f
(
∣
T
∣
)
m
i
n
(
T
)
=
∑
T
⊆
S
(
−
1
)
∣
T
∣
−
k
C
∣
T
∣
−
1
k
−
1
m
i
n
(
T
)
kthmax(S)=\sum_{T\subseteq S}f(|T|)min(T)\\=\sum_{T\subseteq S}(-1)^{|T|-k}C_{|T|-1}^{k-1}min(T)
kthmax(S)=T⊆S∑f(∣T∣)min(T)=T⊆S∑(−1)∣T∣−kC∣T∣−1k−1min(T)
有一道例题,还没弄懂,不会做:重返现世
这道题是ypl学长的神题QwQ
upd:写了这一题,贴一个题解吧
边界需要仔细考虑,非常巧妙地设成-1。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1005,M=10005,K=15,mod=998244353;
int n,m,k,ans,p[N],f[2][M][K];
int fastpow(int a,int x){
int res=1;
while(x){
if(x&1){
res=1LL*res*a%mod;
}
x>>=1;
a=1LL*a*a%mod;
}
return res;
}
int main(){
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<=k;i++){
f[0][0][i]=-1;
}
int sta=1;
for(int i=1;i<=n;i++,sta^=1){
for(int j=0;j<=m;j++){
for(int x=1;x<=k;x++){
f[sta][j][x]=f[sta^1][j][x];
if(j-p[i]>=0){
f[sta][j][x]=(1LL*f[sta][j][x]+f[sta^1][j-p[i]][x-1]-f[sta^1][j-p[i]][x])%mod;
}
}
}
}
for(int i=1;i<m;i++){
ans=(ans+1LL*fastpow(i,mod-2)*m%mod*f[sta^1][i][k]%mod)%mod;
}
ans=(ans+f[sta^1][m][k])%mod;
printf("%d\n",(ans+mod)%mod);
return 0;
}