题目大意
问多少边的子集仍然是强联通的。
DP
设f[i]表示点集为i有多少边的子集强联通。
考虑补集转化,如果不是强联通,缩点后会形成一个DAG。用总的减去非法。
考虑容斥,枚举出度为0的点的点集j,那么如果包含奇数个强连通分量,则表示至少j个出度为0,容斥系数为负,否则容斥系数为正。
那么,我们可以设g[i]表示点集为i有多少边的子集形成奇数个互相之间没有边的强联通分量 ,h[i]表示点集为i有多少边的子集形成偶数个互相之间没有边的强联通分量。
g[i]=∑f[j]∗h[i−j]
h[i]=∑f[j]∗g[i−j]
为了不计重,j必须包含i中编号最小的点。
然后f也容易计算了。
f[i]=2i中点互相的连边−∑(h[j]−g[j])∗2i−j中点到i中点的连边
#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define lowbit(x) (x&-x)
using namespace std;
typedef long long ll;
const int maxn=15+10,mo=1000000007;
int cnt[maxn][33000],mi[300],id[33000];
int f[33000],g[33000],h[33000];
int i,j,k,l,t,n,m,tot,top,num,ans;
int main(){
scanf("%d%d",&n,&m);
fo(i,1,n) id[1<<(i-1)]=i;
mi[0]=1;
fo(i,1,m){
mi[i]=(ll)mi[i-1]*2%mo;
scanf("%d%d",&j,&k);
cnt[j][1<<(k-1)]++;
}
fo(i,1,n)
fo(j,1,(1<<n)-1)
cnt[i][j]=cnt[i][lowbit(j)]+cnt[i][j-lowbit(j)];
h[0]=1;
fo(i,1,(1<<n)-1){
if (i==lowbit(i)){
f[i]=g[i]=1;
h[i]=0;
continue;
}
tot=0;
/*fo(j,1,n)
if (i&(1<<(j-1))) tot+=cnt[j][i];*/
k=i;
while (k){
t=lowbit(k);
tot+=cnt[id[t]][i];
k-=t;
}
f[i]=mi[tot];
j=i;
while (1){
j=(j-1)&i;
if (!j) break;
if (j&lowbit(i)){
(g[i]+=(ll)f[j]*h[i-j]%mo);
if (g[i]>=mo) g[i]-=mo;
(h[i]+=(ll)f[j]*g[i-j]%mo);
if (h[i]>=mo) h[i]-=mo;
}
tot=0;
/*fo(k,1,n)
if ((i-j)&(1<<(k-1))) tot+=cnt[k][i];*/
k=i-j;
while (k){
t=lowbit(k);
tot+=cnt[id[t]][i];
k-=t;
}
(f[i]-=(ll)g[j]*mi[tot]%mo)%=mo;
(f[i]+=mo)%=mo;
(f[i]+=(ll)h[j]*mi[tot]%mo);
if (f[i]>=mo) f[i]-=mo;
}
(f[i]+=h[i]);
if (f[i]>=mo) f[i]-=mo;
(f[i]-=g[i])%=mo;
(f[i]+=mo)%=mo;
(g[i]+=f[i]);
if (g[i]>=mo) g[i]-=mo;
}
ans=f[(1<<n)-1];
printf("%d\n",ans);
}