题意:
给你一个
n
n
n个点
m
m
m条边的有向图,无重边自环,求删去一个边集之后整个图强连通的方案数。
n
<
=
15
,
m
<
=
n
(
n
−
1
)
n<=15,m<=n(n-1)
n<=15,m<=n(n−1)。
题解:
这个数据范围一看就很状压,大概率是个
3
n
3^n
3n的题,然而这个题确实是
3
n
3^n
3n的。
我们先考虑是直接算答案还是用总方案减去不合法的方案数。我们发现如果直接算的话,似乎对于一条边加或者不加,很难记录出一个方便转移的状态来快速确定加进一条边之后一个点集是不是强连通的。于是我们就尝试用总的方案数去减去不合法的方案数。总的方案数显然是 2 m 2^{m} 2m,也就是每条边删或者不删都会是一种方案。那么我们考虑计算不合法的答案。
我们来思考一下不合法的图有什么性质。一个不合法的图一定是非强连通的,非强连通的图缩点后会形成一个点数大于 1 1 1的DAG,那么我们就从这里入手。我们最暴力的想法是枚举强连通分量,考虑一些强连通分量构成一个DAG的情况。DAG中一定有出度为0的点,于是我们规定某个强连通分量除了内部连的边以为没有别的出度,而其他的一些强连通分量就可以随便与这个强连通分量相连。但是这样会出现一些重复计算的情况,我们枚举的时候是限制了当前这个强连通分量是没有出度的,但是还有可能此时别的强连通分量中也有点是没有出度的,这时候我们就要用容斥原理来算答案了。我们设 d p [ S ] dp[S] dp[S]表示集合 S S S的点构成一个DAG的方案数, E ( S , T ) E(S,T) E(S,T)表示 S S S集合的所有点到 T T T集合的所有点的边数。我们枚举一个新加进来强连通分量,我们强制规定这些点与其他不在这个点集里的点之间出度是 0 0 0,那么我们利用容斥原理,可以得到一下转移: d p [ S ] = ∑ T ⊂ S , T ≠ ∅ ( − 1 ) ∣ T − 1 ∣ ∗ 2 E ( S − T , T ) ∗ d p [ S − T ] dp[S]=\sum_{T\subset S,T\neq \emptyset}(-1)^{|T-1|}*2^{E(S-T,T)}*dp[S-T] dp[S]=T⊂S,T̸=∅∑(−1)∣T−1∣∗2E(S−T,T)∗dp[S−T] 但是我们终究还是会因为枚举强连通分量而复杂度爆炸。
但是我们并不直接否定这个想法,它还有优化的空间。我们知道之前复杂度爆炸是因为我们要去枚举强连通分量,那么这次我们枚举一个点集,直接让这个点集是若干个强连通分量缩点后的集合。根据之前的容斥原理,如果连通块个数是奇数,那么容斥系数是 1 1 1,偶数的话是 − 1 -1 −1。我们设 g ( S ) g(S) g(S)为把 S S S划分成若干个强连通分量的方案数,算的时候是带着容斥系数的,设 f ( S ) f(S) f(S)表示 S S S的强连通子图的个数,也就是答案。我们计算 g ( S ) g(S) g(S)的时候还是枚举 T ⊂ S , T ≠ ∅ T\subset S,T\neq \emptyset T⊂S,T̸=∅,表示多了一个连通块 T T T之后的答案,由于比之前多一个连通块,所以容斥系数是之前答案容斥系数的相反数,于是我们减去原来答案。我们有 g ( S ) = f ( S ) − ∑ T ⊂ S , T ≠ ∅ g ( S − T ) f ( T ) g(S)=f(S)-\sum_{T\subset S,T\neq \emptyset}g(S-T)f(T) g(S)=f(S)−∑T⊂S,T̸=∅g(S−T)f(T).我们再考虑算 f ( S ) f(S) f(S),我们设 h ( S ) h(S) h(S)为 S S S这个集合内的边数,那么由于我们在算 g ( S ) g(S) g(S)的时候已经容斥了,所以这时候枚举 g g g的集合是 T T T,那么我们可以直接在 S − T S-T S−T内部任意连边、并且可以和 T T T任意连边。于是有 f ( S ) = 2 h ( S ) − ∑ T ⊂ S , T ≠ ∅ g ( T ) ∗ 2 h ( S − T ) + E ( S − T , T ) f(S)=2^{h(S)}-\sum_{T\subset S,T\neq \emptyset}g(T)*2^{h(S-T)+E(S-T,T)} f(S)=2h(S)−∑T⊂S,T̸=∅g(T)∗2h(S−T)+E(S−T,T). 我们可以预处理出边数的情况,也可以边枚举集合边利用位运算来计算。最后 f ( 2 n − 1 ) f(2^n-1) f(2n−1)就是答案。
这样我们就是枚举集合再枚举子集,复杂度是 O ( 3 n ) O(3^n) O(3n)。
这个题我感觉理解的还是不太好,有些地方可能我也不是完全明白了,如果有地方讲错了还请指正啊qwq
代码:
#include <bits/stdc++.h>
using namespace std;
int n,m;
int cnt[2000010],in[2000010],out[2000010],h[2000010],p[2000010];
long long f[2000010],g[2000010],mi[200];
const long long mod=1e9+7;
inline int calc(int x)
{
int res=0;
while(x)
{
if(x&1)
++res;
x/=2;
}
return res;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i)
{
int x,y;
scanf("%d%d",&x,&y);
x=(1<<(x-1));
y=(1<<(y-1));
out[x]|=y;
in[y]|=x;
}
mi[0]=1;
for(int i=1;i<=n*n;++i)
mi[i]=mi[i-1]*2%mod;
int mx=(1<<n)-1;
for(int i=1;i<=mx;++i)
cnt[i]=calc(i);
for(int i=1;i<=mx;++i)
{
int ji=i-(i&(-i));
for(int j=ji;j;j=(j-1)&ji)
g[i]=(g[i]-f[i^j]*g[j]%mod+mod)%mod;
h[i]=h[ji]+cnt[in[i&(-i)]&ji]+cnt[out[i&(-i)]&ji];//¿¼ÂǼÓÉÏ×îºóÒ»¸öÊýµÄÓ°Ïì
f[i]=mi[h[i]];
for(int j=i;j;j=(j-1)&i)
{
if(i==j)
p[j]=0;
else
{
int k=(i^j)&(-(i^j));
p[j]=p[j^k]+cnt[out[k]&j]-cnt[in[k]&(j^i)];//¿¼ÂǼÓÉÏ×îºóÒ»¸öÊýµÄÓ°Ïì
}
f[i]=(f[i]-mi[h[i^j]+p[j]]*g[j]%mod+mod)%mod;
}
g[i]=(g[i]+f[i])%mod;
}
printf("%lld\n",f[mx]);
return 0;
}