2024FJCPC-H.萤火的意志-min-max容斥、Prufer序列(Cayley定理)、高维前缀和

题目:https://pintia.cn/market/item/1795304158332379136
题意:有一个 n(1n109) 个点的完全图,其中有 m(m20) 条特殊的边。每次操作会等概率地选择这张图的一棵生成树,然后将树上的边染色。问期望多少次,能把所有 m 条特殊边都染上色。


fe 表示边 e 第一次被染色的时间,特殊边的集合为 S,经典min-max容斥:
答案是

E(maxeSfe)=E(TS(1)|T|1mineTfe)=TS(1)|T|1E(mineTfe)

考虑 mineTfe 的含义,即对这个边集来说,有某条边被染色(选中)的时间,这是经典的几何分布——假设选中 T 中某条边的概率是 pT,则期望次数是 i=1i×(1pT)i1pT=1pT
因此只要计算概率 pT 即可

要算选中至少一条边的概率,再容斥一下:设 Xe 表示事件“边 e 被选中”,则要求的是:

pT=P( eTXe )=UT,U(1)|U|1P( eUXe )

转换成算某个边集所有边都被选中的概率,注意这里可千万别觉得好像“绕回去了”,一开始的问题是所有边都被选中的次数期望,这里被我们用两次容斥转换成了一个“简单”得多的概率问题。

这是经典的图连通计数,强制让 U 中的边连接,问有多少种加边方案能得到一棵树,假设有 k 个连通块,其大小分别是 s1,,sk,则方案是 nk2si,因此概率就是 nnksi.

这样我们可以在 O(2mmα(n)) 的时间得到每个 P(eUXe) 的答案,然后要得到 pT,是个经典高维前缀和(参考:https://www.cnblogs.com/heyuhhh/p/11585358.html

实现

注意点: 第二个容斥一定要记得 U,我debug的大部分时间都花在这了…

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define fastio ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0)
using namespace std;
typedef long long ll;
const int MOD=1e9+7;
const int M=20;
int ksm(int a,int b){
int ret=1;a%=MOD;
for(;b;b>>=1,a=(ll)a*a%MOD)if(b&1)ret=(ll)ret*a%MOD;
return ret;
}
int n,m,u[M+1],v[M+1],f[(1<<M)+5],g[(1<<M)+5];
int fa[M*2+5],sz[M*2+5];
vector<int> b;
int find(int x){
if(x==fa[x])return x;
return fa[x]=find(fa[x]);
}
bool merge(int x,int y){
int fx=find(x),fy=find(y);
if(fx==fy)return false;
fa[fy]=fx;
sz[fx]+=sz[fy];
return true;
}
void dsu_init(){
rep(i,0,b.size())fa[i]=i,sz[i]=1;
}
void add(int &x,int y){
x+=y;
if(x>=MOD)x-=MOD;
}
int main(){
fastio;
cin>>n>>m;
rep(i,0,m-1){
cin>>u[i]>>v[i];
b.push_back(u[i]);
b.push_back(v[i]);
}
sort(b.begin(),b.end());
b.erase(unique(b.begin(),b.end()),b.end());
rep(i,0,m-1){
u[i]=lower_bound(b.begin(),b.end(),u[i])-b.begin();
v[i]=lower_bound(b.begin(),b.end(),v[i])-b.begin();
}
for(int S=0;S<(1<<m);S++){
dsu_init();
bool ok=true;
rep(i,0,m-1)if((S>>i)&1)ok&=merge(u[i],v[i]);
if(!ok)f[S]=0;
else{
f[S]=1;
int block=n-b.size();
rep(i,0,b.size()-1)if(find(i)==i){
block++;
f[S]=(ll)f[S]*sz[i]%MOD;
}
f[S]=(ll)f[S]*ksm(ksm(n,n-block),MOD-2)%MOD;
if((__builtin_popcount(S)&1)==0)f[S]=MOD-f[S];
}
g[S]=f[S];
}
rep(j,0,m-1)rep(S,0,(1<<m)-1)if(S>>j&1)add(f[S],f[S^(1<<j)]);
rep(S,0,(1<<m)-1)add(f[S],MOD-g[0]);
rep(S,0,(1<<m)-1)f[S]=ksm(f[S],MOD-2);
int ans=0;
rep(S,0,(1<<m)-1){
if(__builtin_popcount(S)&1)add(ans,f[S]);
else add(ans,MOD-f[S]);
}
cout<<ans;
return 0;
}
posted @   yoshinow2001  阅读(29)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
历史上的今天:
2022-05-29 [面向对象课]复数的输入输出
点击右上角即可分享
微信分享提示