题目描述
传送门
题目大意:有多少边的子集删去之后整个图仍然强联通
题解
直接求强联通的子图不是很好求,所以我们考虑用子图的个数-不强联通的子图个数。
不强联通的子图有什么特点呢?将强连通分量缩点后会形成一个节点数>=2的DAG。
一个DAG肯定由一些没有出度的点构成,那么我们可以考虑枚举没有出度的点的子集T。那么S-T中的边和S-T 到T之间的边也可以随便连。但是我们这样连出来不能保证S-T中没有出度为0的点(或者强联通分量)。而且有可能会重复计算。比如说两个出度为0的点u,v,枚举T={u}的和T={v}时都会计算u,v。
那么我们就可以进行容斥求解。
f(s)
表示s构成DAG的方案数。
f(S)=∑T∈S,T!=0(−1)|T|−1∗2way(S−T,T)+h(S−T)∗f(T)
其中 way(S−T,T)表示S−T到T的边数,h(S−T)表示S−T中的边数
我们维护 g(t) 表示i集合的点缩点后成为奇数个彼此没有边的点的方案数, c(t) 表示i集合的点缩点后成为偶数个彼此没有边的点的方案数,重新定义 f(t) 表示t是强联通子图的方案数。
g(t)=∑f(j)∗c(t−j)
c(t)=∑f(j)∗g(t−j)
那么递推的式子就是
f(s)=2h(s)+∑(c(j)−g(j))∗2way(s−j,j)+h(s−j)
代码
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdio>
#define LL long long
#define N 16
#define p 1000000007
using namespace std;
LL cnt[N][1<<16],mi[403],h[1<<16],g[1<<16],c[1<<16],f[1<<16];
int n,m;
int main()
{
freopen("a.in","r",stdin);
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;
//for (int i=0;i<(1<<n);i++) cout<<h[i]<<" ";
//cout<<endl;
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]);
}