图计数 学习笔记
ini2015_____ · · 算法·理论
突然发现自己不会图计数,这也太倒闭了。
这里简单讲一些
无向图计数
例题 1
P10982 修改版
给你一个
::::info[solution] 连通性感觉是难以处理的,我们考虑单步容斥,求出来不连通的方案数。
记
我们记
统计
然后
例题 2
ARC105F
给你一个
::::info[solution] 直接计数很困难,我们考虑保证图是一个二分图,然后容斥计算出连通的情况数。
记
那么和上面一样,显然有
计算
这个时候,对于每个
const int N=19,M=(1<<18)+5,mod=998244353,iv2=(mod+1)>>1;
int n,m,T;
int f[M],g[M];
int to[N],pw[M];
#define popc(x) __builtin_popcount(x)
#define lowbit(x) (x&(-x))
inline void upd(int& x){if(x>=mod)x-=mod;}
inline int E(int S,int T){
int res=0;
for(int i=0;i<n;i++)if(S>>i&1)res+=popc(to[i]&T);
return res;
}
int main(){
n=read(),m=read();
pw[0]=1; for(int i=1;i<M;i++)upd(pw[i]=pw[i-1]*2);
while(m--){
int a=read()-1,b=read()-1;
to[a]|=1<<b,to[b]|=1<<a;
}
for(int s=0;s<(1<<n);s++)for(int t=s;~t;t=t?((t-1)&s):(-1))upd(g[s]+=pw[E(t,s^t)]);
for(int s=0;s<(1<<n);s++){
f[s]=g[s]; int u=lowbit(s);
for(int t=(s-1)&s;t;t=(t-1)&s)if((t|u)==t)
upd(f[s]+=mod-1ll*f[t]*g[s^t]%mod);
}
printf("%lld\n",1ll*f[(1<<n)-1]*iv2%mod);
return 0;
}
// Think twice,code once
::::
有向图计数
例题 3
P6846
给你一个
::::info[solution]
考虑记
枚举
这个其实是
经典二项式定理,答案为
也就是每个图都只会被算一次,不会算重。 ::: 那么有
那么我们提前预处理哪些集合是独立集,然后就随便 DP 了。
时间复杂度
然后原题要多乘一个
const int N=19,M=(1<<18)+5,mod=998244353,iv2=(mod+1)/2;
int n,m,T;
bool h[M]; int f[M];
#define popc(x) __builtin_popcount(x)
inline void upd(int& x){if(x>=mod)x-=mod;}
int main(){
n=read(),m=read();
for(int i=1;i<=m;i++){
int u=read()-1,v=read()-1;
h[(1<<u)|(1<<v)]=1;
}
for(int i=0;i<n;i++)for(int j=0;j<(1<<n);j++)if(j>>i&1)h[j]|=h[j^(1<<i)];
f[0]=1;
for(int S=0;S<(1<<n);S++){
for(int T=S;T;T=(T-1)&S)if(!h[T]){
if(popc(T)&1)
upd(f[S]+=f[S^T]);
else
upd(f[S]+=mod-f[S^T]);
}
}
printf("%lld\n",1ll*f[(1<<n)-1]*m%mod*iv2%mod);
return 0;
}
// Think twice,code once
::::
例题 4
主旋律
给你一个
::::info[solution] 考虑把图进行缩点,那么合法当且仅当缩完之后是一个单点。
我们记
考虑不强连通等价于缩点之后存在多个点,我们拆掉那些不是全集,且入度为
我们记
我们对于一个
那么有
然后
const int mod=1e9+7;
const int N=15,M=1<<N,inf=1e9+7;
int n,m;
int f[M],g[M],h[M];
int to[N];
int pw[M];
#define popc(x) __builtin_popcount(x)
#define lowbit(x) (x&(-x))
inline void upd(int& x){if(x>=mod)x-=mod;}
int E(int S,int T){
int res=0;
for(int i=0;i<n;i++)if(S>>i&1)res+=popc(to[i]&T);
return res;
}
int main(){
n=read(),m=read();
pw[0]=1; for(int i=1;i<M;i++)pw[i]=pw[i-1]*2%mod;
while(m--){
int u=read()-1,v=read()-1;
to[u]|=(1<<v);
}
for(int s=1;s<(1<<n);s++){
int u=lowbit(s);
for(int t=(s-1)&s;t;t=(t-1)&s)if(t&u)
upd(h[s]+=mod-1ll*f[t]*h[s^t]%mod);
for(int t=s;t;t=(t-1)&s)
upd(g[s]+=1ll*h[t]*pw[E(s,s^t)]%mod);
upd(f[s]=pw[E(s,s)]-g[s]+mod),upd(h[s]+=f[s]);
}
printf("%d\n",f[(1<<n)-1]);
return 0;
}
::::
习题
习题 1
ABC306H
给你
::::info[solution] 典完了,相信大家在学会主旋律的基础上都随便秒掉这个题。
考虑题意就是无向边缩点之后是一个 DAG,我们依旧钦定独立集,记
那么依旧
预处理
const int N=17,M=1<<17,mod=998244353;
int n,m,T;
int to[N];
int h[M],f[M];
inline void upd(int& x){if(x>=mod)x-=mod;}
int fa[N];
int find(int x){
if(x==fa[x])return x;
return fa[x]=find(fa[x]);
}
void merge(int x,int y){
x=find(x),y=find(y);
fa[x]=y;
}
int main(){
n=read(),m=read();
while(m--){
int a=read()-1,b=read()-1;
to[a]|=1<<b,to[b]|=1<<a;
}
for(int s=1;s<(1<<n);s++){
for(int i=0;i<n;i++)fa[i]=i;
for(int i=0;i<n;i++)if(s>>i&1)
for(int j=0;j<n;j++)if(to[i]>>j&1)if(s>>j&1)
merge(i,j);
h[s]=mod-1;
for(int i=0;i<n;i++)if(s>>i&1)if(fa[i]==i)h[s]=mod-h[s];
}
f[0]=1;
for(int s=1;s<(1<<n);s++)
for(int t=s;t;t=(t-1)&s)
upd(f[s]+=1ll*h[t]*f[s^t]%mod);
printf("%d\n",f[(1<<n)-1]);
}
::::
习题 2
重塑时光
给你一个
::::info[solution]
不难发现一个切割方案能插板法对应到序列上放了
不妨认为我们在给
对于一个合法的染色方案,我们还可以给每个集合选择一个拓扑序,那么会有一个所有导出子图拓扑序个数之积的系数,我们要求的就是所有合法方案的系数之和。
首先可以预处理一个
我们记
直接 DP 可以做到
然后你发现瓶颈在于
const int N=17,M=1<<15,mod=1e9+7;
int to[N];
int h[M],g[M][N];
int fv[M],gv[M],a[N],b[N],fx[N];
int st[M];
int fac[N<<1],ifac[N<<1];
int n,m,k;
inline void upd(int& x){if(x>=mod)x-=mod;}
inline int fpow(int a,int b){
int res=1;
for(;b;b>>=1,a=1ll*a*a%mod)if(b&1)res=1ll*a*res%mod;
return res;
}
inline void initfac(){
fac[0]=1; for(int i=1;i<(N<<1);i++)fac[i]=1ll*fac[i-1]*i%mod;
for(int i=0;i<(N<<1);i++)ifac[i]=fpow(fac[i],mod-2);
}
inline bool E(int S,int T){return (st[S]&T);}
void Langrange(){
for(int i=0;i<=n+1;i++){
int v=1;
for(int j=0;j<=n+1;j++)if(j!=i)
v=1ll*v*(i-j+mod)%mod;
v=1ll*fpow(v,mod-2)*fx[i]%mod;
memset(b,0,sizeof b); b[0]=1;
for(int j=0;j<=n+1;j++)if(j!=i){
for(int w=n;~w;w--)
upd(b[w+1]+=b[w]),
b[w]=1ll*b[w]*(mod-j)%mod;
}
for(int j=0;j<=n+1;j++)
upd(a[j]+=1ll*v*b[j]%mod);
}
}
int main(){
n=read(),m=read(),k=read();
initfac();
while(m--){
int a=read()-1,b=read()-1;
to[a]|=1<<b;
}
for(int s=0;s<(1<<n);s++)
for(int i=0;i<n;i++)if(s>>i&1)
st[s]|=to[i];
h[0]=1;
for(int s=1;s<(1<<n);s++)
for(int i=0;i<n;i++)if(s>>i&1)
if(!(to[i]&s))upd(h[s]+=h[s^(1<<i)]);
g[0][0]=mod-1;
for(int s=0;s<(1<<n);s++){
int u=__lg(s&(-s));
for(int t=s;t;t=(t-1)&s)if(t>>u&1)if(!E(t,s^t) && !E(s^t,t))
for(int i=1;i<=n+1;i++)
upd(g[s][i]+=mod-1ll*h[t]*g[s^t][i-1]%mod);
}
for(int x=0;x<=n+1;x++){
for(int s=0;s<(1<<n);s++){
gv[s]=0;
for(int i=n+1;~i;i--)
gv[s]=(1ll*gv[s]*x+g[s][i])%mod;
}
fv[0]=1;
for(int s=1;s<(1<<n);s++){
fv[s]=0;
for(int t=s;t;t=(t-1)&s)if(!E(s^t,t))
upd(fv[s]+=1ll*fv[s^t]*gv[t]%mod);
}
fx[x]=fv[(1<<n)-1];
}
Langrange();
int ans=0;
for(int i=1;i<=k+1;i++)
upd(ans+=1ll*a[i]*fac[k+1]%mod*ifac[k+1-i]%mod);
ans=1ll*ans*ifac[n+k]%mod*fac[k]%mod;
printf("%d\n",ans);
}
::::
习题 3
岁月
给你一个
::::info[solution] 首先考虑 C 性质,每条边的边权都相等。那么我们需要求存在一个外向生成树的概率。
不妨枚举可以成为根的集合
考虑
第一个条件就是主旋律了。
第三个条件考虑所有
第二个条件考虑 DP 处理,我们记
第二个条件满足的概率就是
不难发现三个条件相互独立,把三个概率乘在一起就是
直接做是
考虑 DP 式子的组合意义,我们相当于初始在集合
我们把这个问题反过来,对于每个集合求出
这样我们可以把 C 性质做到
我们把这个做法扩展到一般情况。
考虑如何判断
分析 Kruskal 的过程,我们按照边权
我们记
先仿照上面
这里的条件 3 依旧容易处理。
条件 1 是一个主旋律状物,记
对于条件 2,记
然后
那么可以做到
同理可以通过反向 DP 做到
你发现我们单层是
const int N=15,M=1<<N,K=555,mod=1e9+7,iv2=(mod+1)/2;
int n,m,T;
int to[N],cnt[M],bl[N],_bl[N],sc[M],_sc[M],ans[M];
int f[M],g[M],h[M],pw[K],dp[M],be[M];
bool fl[M],vs[M];
#define popc(x) __builtin_popcount(x)
#define lowbit(x) (x&(-x))
#define mem(x) memset(x,0,sizeof(x))
inline void upd(int& x){if(x>=mod)x-=mod;}
void init(){pw[0]=1; for(int i=1;i<K;i++)pw[i]=1ll*pw[i-1]*iv2%mod;}
int sE(int S,int T){int res=0; for(int i=0;i<n;i++)if(S>>i&1)res+=popc(to[i]&T); return res;}
int E(int S,int T){return (cnt[S|T]-cnt[S]-cnt[T])>>1;}
struct Edge{
int a,b,c;
bool operator<(Edge t)const{return c<t.c;}
}e[K];
int fa[N],bel[N];
int find(int x){if(x==fa[x])return x;return fa[x]=find(fa[x]);}
void merge(int x,int y){x=find(x),y=find(y); if(x==y)return ; assert(x<n); assert(y<n);bl[y]|=bl[x]; fa[x]=y;}
void Memset(){mem(fa),mem(bel),mem(to),mem(cnt),mem(bl),mem(_bl),mem(sc),mem(_sc),mem(ans),mem(f),mem(g),mem(h),mem(fl),mem(vs),mem(dp),mem(be);}
void solve(){
Memset();
n=read(),m=read(); int U=(1<<n)-1;
for(int i=1;i<=m;i++)e[i].a=read()-1,e[i].b=read()-1,e[i].c=read();
sort(e+1,e+1+m);
for(int i=0;i< n;i++)bl[i]=1<<i;
for(int i=0;i< n;i++)ans[1<<i]=1,fa[i]=i;
for(int L=1,R=0;L<=m;L=R+1){
while(R!=m && e[R+1].c==e[L].c)++R;
for(int i=0;i<n;i++)bel[i]=find(i);
mem(to),mem(fl),mem(be),mem(dp),mem(f),mem(g),mem(h),mem(dp);
for(int i=0;i<n;i++)_bl[i]=bl[i];
for(int s=0;s<=U;s++)
for(int i=0;i<n;i++)if(s>>i&1)
_sc[s]|=_bl[bel[i]];
for(int i=L;i<=R;i++)if(bel[e[i].a]!=bel[e[i].b]){
merge(e[i].a,e[i].b);
to[e[i].a]|=1<<e[i].b,to[e[i].b]|=1<<e[i].a;
fl[bl[find(e[i].b)]]=1;
}
for(int s=0;s<=U;s++)
cnt[s]=sE(s,s);
for(int s=0;s<=U;s++)if(fl[s])
for(int t=s;t;t=(t-1)&s)fl[t]=1;
be[0]=1;
for(int s=1;s<=U;s++)if(fl[s]){
int u=__lg(lowbit(s)),su=_bl[bel[u]],st=s&(U^su);
be[s]=1ll*be[st]*ans[su&s]%mod; sc[s]=0;
for(int i=0;i<n;i++)if(s>>i&1)sc[s]|=bl[find(i)];
}
for(int s=1;s<=U;s++)if(fl[s]){
int u=lowbit(s);
for(int t=s;t;t=(t-1)&s)if((t&u)&&(_sc[s^t]&_sc[t])==0)
upd(h[s]+=mod-1ll*f[t]*h[s^t]%mod*pw[E(_sc[s^t],t)+E(s^t,_sc[t])]%mod);
for(int t=s;t;t=(t-1)&s)if((_sc[s^t]&_sc[t])==0)
upd(g[s]+=1ll*h[t]*pw[E(_sc[s^t],t)]%mod);
upd(f[s]=1-g[s]+mod); upd(h[s]+=f[s]);
}
for(int s=0;s<=U;s++)if(fl[s])
if(_sc[s]==sc[s])dp[s]=1;
for(int s=U;s;s--)if(fl[s])
for(int t=(s-1)&s;t;t=(t-1)&s)
if((_sc[t]&_sc[s^t])==0)
upd(dp[s^t]+=mod-1ll*dp[s]*pw[E(_sc[s^t],t)]%mod*be[t]%mod);
for(int s=U;s;s--)if(fl[s])
dp[s]=1ll*dp[s]*be[s]%mod;
for(int s=U;s;s--)if(fl[s]){
ans[s]=0; int qs=s^sc[s];
for(int t=qs;~t;t=t?((t-1)&qs):-1)
if((_sc[s]&_sc[qs^t])==0)
upd(ans[s]+=dp[sc[s]^t]);
ans[s]=1ll*ans[s]*pw[E(sc[s]^_sc[s],s)]%mod*f[s]%mod%mod;
}
}
int res=0; for(int i=0;i<=U;i++)upd(res+=ans[i]);
printf("%d\n",res);
}
signed main(){
init();
int Testid=read(); T=read();
while(T--)solve();
}
::::