分析:
有多少个边的子集删去之后整个图仍然强联通=>整个图有多少个强联通子图
强联通子图的个数比较难计算,我们考虑进行进一步的转化:
强联通子图的个数=>子图个数-不强联通的子图个数
不强联通的子图有什么特点呢?将强连通分量缩点后会形成一个节点数>=2的DAG
那么我们就考虑这个子问题:一些点构成 DAG 的方案数
设原图为 G G ,点集为,边集为 E E ,子集的点集为
一个DAG肯定是由一些没有出度的点组成的,我们可以枚举这些点组成的集合
T
T
那么内的边和
V−T
V
−
T
到
T
T
的边随便要不要都是可以的,但是这样有可能会出现重复的情况
因为在枚举的时候这样计算不能保证
V−T
V
−
T
内没有不存在出度的点,也就是说两个点
u,v
u
,
v
,
T=u
T
=
u
时会计算一次没有出度的点是
u,v
u
,
v
的情况,
T=v
T
=
v
时也会计算一次,因此需要用容斥原理
设
f(S)
f
(
S
)
表示点集
S
S
构成的DAG的方案数,那么可以得到转移
这里
ways(S−T,T)
w
a
y
s
(
S
−
T
,
T
)
表示
S−T
S
−
T
到
T
T
的边数
表示
S−T
S
−
T
中的边数
然后这样由于要枚举强连通分量,复杂度十分高,这题肯定没办法过
换个角度,可以枚举所有没有出度的强连通分量缩成的点的集合
T
T
根据上面的容斥,如果内的点组成奇数个强连通分量,那么对答案的贡献将是1,如果是偶数个那就是−1
维护
g(t)
g
(
t
)
表示集合的点缩点后成为奇数个彼此没有边的点的方案数
c(t)
c
(
t
)
表示集合的点缩点后成为偶数个彼此没有边的点的方案数
重新定义
f(t)
f
(
t
)
表示
t
t
是强联通子图的方案数
(奇数=偶数+1,偶数=奇数+1)
由此得到递推式:
关于 ways w a y s 和 h h 的计算可以在计算的时候顺便计算,这样就可以在 O(3n) O ( 3 n ) 时间内计算出答案了
这道题连理解起来都有点困难,所以还是看看代码吧
for (int i=0;i<(1<<n);i++)
for (int j=0;j<n;j++) {
int t=i&(-i);
cnt[j][i]=(cnt[j][t]+cnt[j][i-t])%p;
}
一开始我们把边都存储成这个形式:
cnt[i][j],i点的后继状态为j
c
n
t
[
i
]
[
j
]
,
i
点
的
后
继
状
态
为
j
在这个循环中,就是在维护
i
i
点的后继状态中有多少个1
i&(-i)
就是把状态
i
i
分成了两个子集
举个小例子:
i= i&(-i)=
4 (100) 4 (100)
5 (101) 1 (1)
12 (1100) 4 (100)
14 (1110) 2 (10)
for (int i=0;i<(1<<n);i++)
for (int j=0;j<n;j++)
if (i&(1<<j)) h[i]=(h[i]+cnt[j][i])%p;
表示点集 i i 内的边数
dp卡着式子来就好了
for (int i=0;i<(1<<n);i++) { //枚举点集 S
if (i==(i&(-i))) {
f[i]=g[i]=1;
continue;
}
f[i]=mi[h[i]]; //2^h(S)
for (int j=i&(i-1);j;j=i&(j-1)) { //枚举S的子集 T
int t=i-j; //t=S-T
int tmp=0;
for (int k=0;k<n;k++)
if (t&(1<<k)) tmp=(tmp+cnt[k][i])%p; //way(S-T,S)
f[i]=(f[i]+mi[tmp]*(c[j]-g[j]))%p; //递推式 2^way(S-T,S)*(c(T)-g(T))
if (j&(i&(-i))) {
c[i]=(c[i]+f[j]*g[t])%p;
g[i]=(g[i]+f[j]*c[t])%p;
}
}
f[i]=(f[i]+c[i])%p; //按照递推式维护g,f,h
f[i]=(f[i]-g[i])%p;
g[i]=(g[i]+f[i])%p;
}
#include<cstdio>
#include<cstring>
#include<iostream>
#define ll long long
using namespace std;
const ll p=1e9+7;
const int N=16;
ll cnt[N][1<<16],mi[405],h[1<<16],g[1<<16],c[1<<16],f[1<<16];
int n,m;
int main()
{
scanf("%d%d",&n,&m);
mi[0]=1;
for (int i=1;i<=300;i++) mi[i]=mi[i-1]*2%p;
for (int i=1;i<=m;i++) {
int x,y;
scanf("%d%d",&x,&y);
x--; y--; cnt[x][1<<y]++; //后继结点
}
for (int i=0;i<(1<<n);i++)
for (int j=0;j<n;j++) {
int t=i&(-i); //子集
cnt[j][i]=(cnt[j][t]+cnt[j][i-t])%p;
}
for (int i=0;i<(1<<n);i++)
for (int j=0;j<n;j++)
if (i&(1<<j)) h[i]=(h[i]+cnt[j][i])%p;
c[0]=1;
for (int i=0;i<(1<<n);i++) {
if (i==(i&(-i))) {
f[i]=g[i]=1;
continue;
}
f[i]=mi[h[i]];
for (int j=i&(i-1);j;j=i&(j-1)) { //枚举子集
int t=i-j;
int tmp=0;
for (int k=0;k<n;k++)
if (t&(1<<k)) tmp=(tmp+cnt[k][i])%p;
f[i]=(f[i]+mi[tmp]*(c[j]-g[j]))%p;
if (j&(i&(-i))) {
c[i]=(c[i]+f[j]*g[t])%p;
g[i]=(g[i]+f[j]*c[t])%p;
}
}
f[i]=(f[i]+c[i])%p;
f[i]=(f[i]-g[i])%p;
g[i]=(g[i]+f[i])%p;
}
int t=(1<<n)-1;
f[t]=(f[t]%p+p)%p;
printf("%lld\n",f[t]);
return 0;
}