首先我们摆出公式好了:
然后进阶一点的话, 也是可以求的:
上述式子在期望意义下全部成立。
极其优美的东西。
1. Luogu P4707 重返现世
这个题对我来讲很牛逼,然而不过是 min-max 容斥的入门练习题罢了......
有了是 容斥的提示后这题并不难想。设 ,其中 代表第 种原料第一次获得的时间。令 那么求的就是 。
然后套用 容斥,我们考虑 怎么算,事实上根据一点极限知识可以算出来答案是 。
容斥到这里基本就是要 dp 了,发现 然后 的限制很显眼。考虑设一个 的 。这个 dp 就随便做了。
min-max 容斥中的 dp 要注意到的是边界,因为 的限制,往往边界要设置成奇怪的东西。为了避免这些麻烦推荐特判 的转移。
#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 按位或
发现比上面一个还板......
同样地设 是位置 的出现时间然后 转为求 ,这里算一下概率,期望同样是倒数。然后概率的计算就容斥一步,计算高维前缀和就行了。
//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-max容斥后的步骤和普通的容斥差不多也)。
然后数据范围告诉我们要搞一个 3 方的 dp。把 作为状态的值,然后状态里加入一个 表示线段数目。就是说设 是前 个点, 中点所在线段数目为 的所有情况的容斥系数和。
然后这个东西发现不好转移,可以先修改状态让第 个点一定被选进 中。然后, 枚举 中的上一个点,去重是容易的... 至于前驱状态对当前状态的贡献,由于你存的是容斥系数 的和,显然当加入第 个数后 增加了 所以贡献就是原来的值 。
然后 min-max 容斥做 dp 之前都想一下要不要特判边界情况。因为常规多步容斥是说“条件是否满足”来决定集合大小的,自然 个条件满足就代表全体。但是 到底是啥意思呢...
代码超级好写。
#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 随机游走
板子题二合一。推出来 的做法然后被正解惊到了。
感觉这四题的 min-max 容斥都长得一模一样.... 实质上是考虑计算 代表 第一次走到 中某个点的期望次数。
然后我们把根定做 ,你发现 中祖孙关系的两个点,可以只保留祖先。换言之你可以看作此时在一棵树上,从根开始走,一直走到某个叶子的期望次数。
这个肯定没法快速推了啊,考虑直接设 表示从 开始走的答案,然后如果是叶子那么 。
考虑非叶子节点也很简单,就是 ,如果 不存在那么 也是显然正确的。
但这东西没办法直接算啊...... naive 的想法是非叶子的 全部搞成未知量,然后每个非叶子节点都能搞出一个方程来,可以大力高斯消元搞出唯一解。这样可以干出一个 的 30 分,结合其它的白给的似乎可以搞到 分,其实收益也挺不错了......
然后就是个纯套路部分了,因为是在树上,所以有个套路叫树上高斯消元。就是说我们从最底层往上反推对吧,那么显然对于一个叶子节点 。然后归纳地去推,假设有个节点 它的所有儿子 都可以表示成 的形式,能不能搞出一个 来,如果能那么 就是我们要的 了。
显然是可以的,把 替换掉后,大力移项就可以得到 。然后你一路向上搞上去就行了...
但是现在如果暴力预处理,对每一个 枚举子集复杂度是 的,好像 有点悬?(不过 似乎也没事)。
但是你发现其实多次问 min-max 容斥的那个式子的值的话实际上就是问你高维前缀和啊,只不过有的是乘一个 去统计罢了,上一个 sos dp 就行。
时间复杂度 。带个 是树上高斯消元的过程要算逆元...
#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通