Cry_For_theMoon  

首先我们摆出公式好了:

\[\max\{S\}=\sum_{T\subset S\land T\neq \empty}(-1)^{|T|-1}\min\{T\} \]

\[\min\{S\}=\sum_{T\subset S\land T\neq \empty}(-1)^{|T|-1}\max\{T\} \]

然后进阶一点的话,\(k-th \min/\max\) 也是可以求的:

\[\max_{k}\{S\}=\sum_{T\subset S\land T\neq \empty}(-1)^{|T|-k}\dbinom{|T|-1}{k-1}\min\{T\} \]

\[\min_{k}\{S\}=\sum_{T\subset S\land T\neq \empty}(-1)^{|T|-k}\dbinom{|T|-1}{k-1}\max\{T\} \]

上述式子在期望意义下全部成立。

极其优美的东西。

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;
} 
posted on 2022-03-30 22:08  Cry_For_theMoon  阅读(141)  评论(0)    收藏  举报