昨天考试第二题与此题相似….(话说这还是NOIP模拟赛???),所以才找了此题….一开始没意识到是集训…等我发现的时候…2333….还是看了题解…..
分析:
其实此题简化一下就是说有多少个生成子图是强连通的….
(所谓生成子图就是只能去边不能去点的子图)一看数据范围,妥妥的状压DP…
f[i]代表i点集的强连通生成子图数量…
由于求强连通生成子图略麻烦(略??…)所以我们转化为求非强连通的生成子图的数量….
我们如果把一个非强连通的有向图缩一缩点…一定存在一些入度为0的点…所以我们只要枚举这些点就好(以为可能会有重复的…那就通过容斥原理来解决)
M[i]解释起来略麻烦…
假设i={1,2,3}…
M[i]=cnt[{1},{2},{3}]-cnt[{1,2},{3}]-cnt[{1},{2,3}]-cnt[{1,3},{2}]+cnt[{1,2,3}](+-就是容斥原理)
cnt[{1},{2},{3}]代表的是123分别是缩完点之后入度为0的点…那么cnt[{1},{2},{3}]就代表当前状态的子图的数量…
G[i]代表的是i的边集的数量….
然后计算的时候我们只需要计算这些入度为0的点向其他点的连边….
代码如下:
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
//by NeighThorn
using namespace std;
const int MOD=1000000007;
int n,m,in[1<<15],out[1<<15],num[1<<15],cnt[1<<15];//num[i]代表i集合有几个点,cnt[i]代表入度为0的强连通分量到剩下的节点的边数
long long f[1<<15],M[1<<15],G[1<<15];//f[i]代表i的生成子图是强连通的方案数,M[i]枚举的是i缩点之后的DAG中的入度为0的强连通分量,G[i]代表的是i的边集的数量
long long power[15*15];
signed main(void){
scanf("%d%d",&n,&m);power[0]=1;
for(int i=1;i<=n*n;i++)
power[i]=power[i-1]*2%MOD;
for(int i=1;i<1<<n;i++)
num[i]=num[i-(i&-i)]+1;
for(int i=1,x,y;i<=m;i++)
scanf("%d%d",&x,&y),out[1<<x-1]|=1<<y-1,in[1<<y-1]|=1<<x-1;
f[0]=1,M[0]=MOD-1,G[0]=1;
for(int i=1;i<1<<n;i++){
if(num[i]==1){
f[i]=M[i]=G[i]=1;
continue;
}
int x=i&-i,v=i-x,edgenum=0;
for(int j=0;j<n;j++)
if(i&(1<<j))
edgenum+=num[out[1<<j]&i];
//cout<<edgenum<<endl;
G[i]=f[i]=power[edgenum],M[i]=0;//f[i]=所有的子图-不合法的子图
for(int j=v;;j=(j-1)&v){
int now=j|x;
if(now!=i)
M[i]=(M[i]-f[now]*M[i-now]%MOD+MOD)%MOD;//容斥原理,f[now]本身就是一个强连通图,M[i-now]就是一个子问题,因为多了now这个强连通分量奇偶性改变所以-
if(j==0)
break;
}
int sum=0;//不合法的
for(int j=i;j;j=(j-1)&i){
if(j==i)
cnt[j]=0;
else{
int lala=(i-j)&(-(i-j)),lalala=j|lala;
cnt[j]=cnt[lalala]-num[out[lala]&(i-lalala)]+num[in[lala]&j];
}
sum=(sum+M[j]*G[i-j]%MOD*power[cnt[j]]%MOD)%MOD;
}
f[i]=(f[i]-sum+MOD)%MOD,M[i]=(M[i]+f[i])%MOD;
}
cout<<f[(1<<n)-1]<<endl;
return 0;
}
by >_< NeighThorn