Cry_For_theMoon  

首先我们摆出公式好了:

max{S}=TST(1)|T|1min{T}

min{S}=TST(1)|T|1max{T}

然后进阶一点的话,kthmin/max 也是可以求的:

maxk{S}=TST(1)|T|k(|T|1k1)min{T}

mink{S}=TST(1)|T|k(|T|1k1)max{T}

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

极其优美的东西。

1. Luogu P4707 重返现世

这个题对我来讲很牛逼,然而不过是 min-max 容斥的入门练习题罢了......

有了是 minmax 容斥的提示后这题并不难想。设 S={s1,s2,...,sn},其中 si 代表第 i 种原料第一次获得的时间。令 k=nk+1 那么求的就是 E(maxk{S})

然后套用 minmax 容斥,我们考虑 E(min{T}) 怎么算,事实上根据一点极限知识可以算出来答案是 miTpi

容斥到这里基本就是要 dp 了,发现 k10 然后 m 104 的限制很显眼。考虑设一个 n×k×mdp。这个 dp 就随便做了。

min-max 容斥中的 dp 要注意到的是边界,因为 T 的限制,往往边界要设置成奇怪的东西。为了避免这些麻烦推荐特判 |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 按位或

发现比上面一个还板......

同样地设 si 是位置 i 的出现时间然后 max{si} 转为求 min{si},这里算一下概率,期望同样是倒数。然后概率的计算就容斥一步,计算高维前缀和就行了。

//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。把 (1)|T|1 作为状态的值,然后状态里加入一个 j 表示线段数目。就是说设 f(i,j) 是前 i 个点,T 中点所在线段数目为 j 的所有情况的容斥系数和。

然后这个东西发现不好转移,可以先修改状态让第 i 个点一定被选进 T 中。然后, O(n) 枚举 T 中的上一个点,去重是容易的... 至于前驱状态对当前状态的贡献,由于你存的是容斥系数 (1)|T|1 的和,显然当加入第 i 个数后 |T| 增加了 1 所以贡献就是原来的值 ×(1)

然后 min-max 容斥做 dp 之前都想一下要不要特判边界情况。因为常规多步容斥是说“条件是否满足”来决定集合大小的,自然 0 个条件满足就代表全体。但是 max(min){} 到底是啥意思呢...

代码超级好写。

#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(n32n) 的做法然后被正解惊到了。

感觉这四题的 min-max 容斥都长得一模一样.... 实质上是考虑计算 min{T} 代表 x 第一次走到 T 中某个点的期望次数。

然后我们把根定做 x,你发现 T 中祖孙关系的两个点,可以只保留祖先。换言之你可以看作此时在一棵树上,从根开始走,一直走到某个叶子的期望次数。

这个肯定没法快速推了啊,考虑直接设 dp(u) 表示从 u 开始走的答案,然后如果是叶子那么 dp(u)=0

考虑非叶子节点也很简单,就是 dp(u)=dp(fa)+dp(v)degu+1,如果 fa 不存在那么 dp(fa)=0 也是显然正确的。

但这东西没办法直接算啊...... naive 的想法是非叶子的 dp 全部搞成未知量,然后每个非叶子节点都能搞出一个方程来,可以大力高斯消元搞出唯一解。这样可以干出一个 n10 的 30 分,结合其它的白给的似乎可以搞到 70 分,其实收益也挺不错了......

然后就是个纯套路部分了,因为是在树上,所以有个套路叫树上高斯消元。就是说我们从最底层往上反推对吧,那么显然对于一个叶子节点 dp(u)=0×dp(fa)+0。然后归纳地去推,假设有个节点 u 它的所有儿子 v 都可以表示成 dp(v)=kv×dp(u)+bv 的形式,能不能搞出一个 dp(u)=ku×dp(fa)+bu 来,如果能那么 bx 就是我们要的 min{T} 了。

显然是可以的,把 dp(v) 替换掉后,大力移项就可以得到 ku=1degusumk,bu=degu+sumbdegusumk。然后你一路向上搞上去就行了...

但是现在如果暴力预处理,对每一个 S 枚举子集复杂度是 O(3n) 的,好像 n=18 有点悬?(不过 3×107 似乎也没事)。

但是你发现其实多次问 min-max 容斥的那个式子的值的话实际上就是问你高维前缀和啊,只不过有的是乘一个 1 去统计罢了,上一个 sos dp 就行。

时间复杂度 O(n2nlogp+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   Cry_For_theMoon  阅读(111)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
 
点击右上角即可分享
微信分享提示