首先我们摆出公式好了:
然后进阶一点的话,\(k-th \min/\max\) 也是可以求的:
上述式子在期望意义下全部成立。
极其优美的东西。
1. Luogu P4707 重返现世
这个题对我来讲很牛逼,然而不过是 min-max 容斥的入门练习题罢了......
有了是 \(min-max\) 容斥的提示后这题并不难想。设 \(S=\{s_1,s_2,...,s_n\}\),其中 \(s_i\) 代表第 \(i\) 种原料第一次获得的时间。令 \(k=n-k+1\) 那么求的就是 \(E(\max_{k}\{S\})\)。
然后套用 \(min-max\) 容斥,我们考虑 \(E(\min\{T\})\) 怎么算,事实上根据一点极限知识可以算出来答案是 \(\frac{m}{\sum_{i\in T}p_i}\)。
容斥到这里基本就是要 dp 了,发现 \(k\le 10\) 然后 \(m\) \(\le 10^4\) 的限制很显眼。考虑设一个 \(n\times k\times m\) 的 \(dp\)。这个 dp 就随便做了。
min-max 容斥中的 dp 要注意到的是边界,因为 \(T\neq \empty\) 的限制,往往边界要设置成奇怪的东西。为了避免这些麻烦推荐特判 \(|T|=1\) 的转移。
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define op(x) ((x&1)?x+1:x-1)
#define odd(x) (x&1)
#define even(x) (!odd(x))
#define lc(x) (x<<1)
#define rc(x) (lc(x)|1)
#define lowbit(x) (x&-x)
#define mp(x,y) make_pair(x,y)
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
using namespace std;
const int MAXN=1e3+10,MAXK=15,MAXM=1e4+10,LIM=1e4,mod=998244353;
ll mypow(ll a,ll n){
if(!n)return 1;
ll tmp=mypow(a,n/2);tmp=tmp*tmp%mod;
if(n&1)tmp=tmp*a%mod;return tmp;
}
ll inv[MAXM],n,k,m,p[MAXN],f[2][MAXM][MAXK];
void add(ll& x,ll y){x=(x+y)%mod;}
int main(){
rep(i,1,LIM)inv[i]=mypow(i,mod-2);
cin>>n>>k>>m;k=n-k+1;rep(i,1,n)cin>>p[i];
rep(i,1,n){
memset(f[i&1],0,sizeof f[i&1]);
rep(j,1,m){
rep(K,1,k){
add(f[i&1][j][K],f[(i-1)&1][j][K]);
if(p[i]==j)add(f[i&1][j][K],(K==1));
else if(p[i]<j){
add(f[i&1][j][K],mod-f[(i-1)&1][j-p[i]][K]);
add(f[i&1][j][K],f[(i-1)&1][j-p[i]][K-1]);
}
}
}
}
ll ans=0;
rep(j,1,m){
ll res=f[n&1][j][k]*m%mod*inv[j]%mod;
add(ans,res);
}
cout<<ans<<endl;
return 0;
}
2. HAOI2015 按位或
发现比上面一个还板......
同样地设 \(s_i\) 是位置 \(i\) 的出现时间然后 \(\max\{s_i\}\) 转为求 \(\min\{s_i\}\),这里算一下概率,期望同样是倒数。然后概率的计算就容斥一步,计算高维前缀和就行了。
//HAOI,2015
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define op(x) ((x&1)?x+1:x-1)
#define odd(x) (x&1)
#define even(x) (!odd(x))
#define lc(x) (x<<1)
#define rc(x) (lc(x)|1)
#define lowbit(x) (x&-x)
#define mp(x,y) make_pair(x,y)
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
using namespace std;
const int MAXN=20,MAXM=(1<<20);
int n,pcnt[MAXM];
db p[MAXM],f[MAXM];
void fwt(){
rep(i,0,(1<<n)-1)f[i]=p[i];
rep(i,0,n-1){
rep(j,0,(1<<n)-1){
if(j>>i&1){
f[j]+=f[j^(1<<i)];
}
}
}
}
int P(int x){
if(even(x))return 1;
return -1;
}
db minv(int mask){
db p=1-f[((1<<n)-1)^mask];
return 1/p;
}
db ans;
int val;
int main(){
cin>>n;
rep(i,1,(1<<n)-1)pcnt[i]=pcnt[i^lowbit(i)]+1;
rep(i,0,(1<<n)-1){
cin>>p[i];
if(p[i]>0)val|=i;
}
fwt();
int S=(1<<n)-1;
if(val!=S){cout<<"INF"<<endl;return 0;}
for(int T=S;T;T=(T-1)&S){
ans+=P(pcnt[T]-1)*minv(T);
}
printf("%.6f",ans);
return 0;
}
3. ABC242H Random Painting
你发现和上面一道题长得非常非常像......
对于 \(\min\{T}\) 这个东西,我们只关注 \(|T|\) 的大小以及和 \(T\) 中点所在的线段数目。而且我们发现 \(|T|\) 这个东西只是决定了贡献的正负。(其实说白了第一步min-max容斥后的步骤和普通的容斥差不多也)。
然后数据范围告诉我们要搞一个 3 方的 dp。把 \(\sum (-1)^{|T|-1}\) 作为状态的值,然后状态里加入一个 \(j\) 表示线段数目。就是说设 \(f(i,j)\) 是前 \(i\) 个点,\(T\) 中点所在线段数目为 \(j\) 的所有情况的容斥系数和。
然后这个东西发现不好转移,可以先修改状态让第 \(i\) 个点一定被选进 \(T\) 中。然后, \(O(n)\) 枚举 \(T\) 中的上一个点,去重是容易的... 至于前驱状态对当前状态的贡献,由于你存的是容斥系数 \((-1)^{|T|-1}\) 的和,显然当加入第 \(i\) 个数后 \(|T|\) 增加了 \(1\) 所以贡献就是原来的值 \(\times (-1)\)。
然后 min-max 容斥做 dp 之前都想一下要不要特判边界情况。因为常规多步容斥是说“条件是否满足”来决定集合大小的,自然 \(0\) 个条件满足就代表全体。但是 \(\max(\min)\{\empty\}\) 到底是啥意思呢...
代码超级好写。
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define op(x) ((x&1)?x+1:x-1)
#define odd(x) (x&1)
#define even(x) (!odd(x))
#define lc(x) (x<<1)
#define rc(x) (lc(x)|1)
#define lowbit(x) (x&-x)
#define mp(x,y) make_pair(x,y)
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
using namespace std;
const int MAXN=410,mod=998244353;
ll n,m,L[MAXN],R[MAXN];
ll num[MAXN][MAXN],f[MAXN][MAXN],ans;
int check(int pos,int L,int R){return pos>=L && pos<=R;}
void add(ll& x,ll y){x=(x+y)%mod;}
ll mypow(ll a,ll n){
if(!n)return 1;
ll tmp=mypow(a,n/2);tmp=tmp*tmp%mod;
if(n&1)tmp=tmp*a%mod;return tmp;
}
int main(){
cin>>n>>m;
rep(i,1,m)cin>>L[i]>>R[i];
rep(i,1,n)rep(j,1,n)rep(k,1,m)num[i][j]+=check(i,L[k],R[k])&check(j,L[k],R[k]);
rep(i,1,n){
add(f[i][num[i][i]],1);
rep(j,num[i][i],m)rep(k,1,i-1){
ll x=j+num[k][i]-num[i][i];
if(x>=0&&x<=m)add(f[i][j],mod-f[k][x]);
}
}
rep(i,1,n)rep(j,1,m)add(ans,f[i][j]*m%mod*mypow(j,mod-2)%mod);
cout<<ans<<endl;
return 0;
}
4. PKUWC2018 随机游走
板子题二合一。推出来 \(O(n^32^n)\) 的做法然后被正解惊到了。
感觉这四题的 min-max 容斥都长得一模一样.... 实质上是考虑计算 \(\min\{T\}\) 代表 \(x\) 第一次走到 \(T\) 中某个点的期望次数。
然后我们把根定做 \(x\),你发现 \(T\) 中祖孙关系的两个点,可以只保留祖先。换言之你可以看作此时在一棵树上,从根开始走,一直走到某个叶子的期望次数。
这个肯定没法快速推了啊,考虑直接设 \(dp(u)\) 表示从 \(u\) 开始走的答案,然后如果是叶子那么 \(dp(u)=0\)。
考虑非叶子节点也很简单,就是 \(dp(u)=\frac{dp(fa)+\sum dp(v)}{deg_u}+1\),如果 \(fa\) 不存在那么 \(dp(fa)=0\) 也是显然正确的。
但这东西没办法直接算啊...... naive 的想法是非叶子的 \(dp\) 全部搞成未知量,然后每个非叶子节点都能搞出一个方程来,可以大力高斯消元搞出唯一解。这样可以干出一个 \(n\le 10\) 的 30 分,结合其它的白给的似乎可以搞到 \(70\) 分,其实收益也挺不错了......
然后就是个纯套路部分了,因为是在树上,所以有个套路叫树上高斯消元。就是说我们从最底层往上反推对吧,那么显然对于一个叶子节点 \(dp(u)=0\times dp(fa)+0\)。然后归纳地去推,假设有个节点 \(u\) 它的所有儿子 \(v\) 都可以表示成 \(dp(v)=k_v\times dp(u)+b_v\) 的形式,能不能搞出一个 \(dp(u)=k_u\times dp(fa)+b_u\) 来,如果能那么 \(b_x\) 就是我们要的 \(\min\{T\}\) 了。
显然是可以的,把 \(dp(v)\) 替换掉后,大力移项就可以得到 \(k_u=\frac{1}{deg_u-sumk},b_u=\frac{deg_u+sumb}{deg_u-sumk}\)。然后你一路向上搞上去就行了...
但是现在如果暴力预处理,对每一个 \(S\) 枚举子集复杂度是 \(O(3^n)\) 的,好像 \(n=18\) 有点悬?(不过 \(3\times 10^7\) 似乎也没事)。
但是你发现其实多次问 min-max 容斥的那个式子的值的话实际上就是问你高维前缀和啊,只不过有的是乘一个 \(-1\) 去统计罢了,上一个 sos dp 就行。
时间复杂度 \(O(n2^n\log p+q)\)。带个 \(\log\) 是树上高斯消元的过程要算逆元...
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define op(x) ((x&1)?x+1:x-1)
#define odd(x) (x&1)
#define even(x) (!odd(x))
#define lc(x) (x<<1)
#define rc(x) (lc(x)|1)
#define lowbit(x) (x&-x)
#define mp(x,y) make_pair(x,y)
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
using namespace std;
const int MAXN=18,MAXM=(1<<18),mod=998244353;
ll mypow(ll a,ll n){
if(!n)return 1;
ll tmp=mypow(a,n/2);tmp=tmp*tmp%mod;
if(n&1)tmp=tmp*a%mod;return tmp;
}
ll inv(ll a){return mypow((a%mod+mod)%mod,mod-2);}
int n,q,s;
ll a[MAXM],f[MAXM];
vector<int>e[MAXN];
array<ll,2> dfs(int mask,int u,int fa){
if(mask>>u&1)return {0,0};
int cnt=e[u].size();ll sumk=0,sumb=0;
for(auto v:e[u])if(v!=fa){
auto tmp=dfs(mask,v,u);
sumk=(sumk+tmp[0])%mod;sumb=(sumb+tmp[1])%mod;
}
return {inv(cnt-sumk),((sumb+cnt)%mod)*inv(cnt-sumk)%mod};
}
void fwt(){
rep(i,1,(1<<n)-1){
if(even(__builtin_popcount(i)))f[i]=(mod-a[i])%mod;
else f[i]=a[i];
}
rep(i,0,n-1)rep(j,1,(1<<n)-1)if(j>>i&1)f[j]=(f[j]+f[j^(1<<i)])%mod;
}
int main(){
ios::sync_with_stdio(false);
cin>>n>>q>>s;s--;
rep(i,1,n-1){
int u,v;cin>>u>>v;u--;v--;
e[u].push_back(v);e[v].push_back(u);
}
rep(mask,1,(1<<n)-1)a[mask]=dfs(mask,s,-1)[1];
fwt();
rep(i,1,q){
int mask=0,len,num;cin>>len;
while(len--){cin>>num;num--;mask^=(1<<num);}
cout<<f[mask]<<endl;
}
return 0;
}

浙公网安备 33010602011771号