UOJ #37. 【清华集训2014】主旋律
首先我们发现对强连通图不太好计数,那么我们对不要求弱联通的非强连通图计数会好做一点,然后用所有的方案减去即可。
容易发现这样的图缩点以后是一个DAG,则可以参照DAG计数的方法,每次枚举入度为\(0\)的点。具体的,我们设\(dp_{S1,S2}\)表示\(S1\)导出子图中入度为\(0\)的点为\(S2\)的方案数。则每次枚举新的入度为\(0\)的点是,每个之前入度为\(0\)的点一定要向现在\(0\)入度的点有一条边。其余边随意即可。但是这样复杂度是\(O(4^n)\)的过不去。
我们考虑进一步容斥:枚举一个子集\(S2\),表示这中间有\(i\)个强连通分量且互相没有边的方案数记为\(g_{S2,i}\),则\(dp\)每次枚举一个\(g_{S2,i}\)转移,但是我们并不能保证只有\(i\)个无入度的点。因此要乘上容斥系数\((-1)^{i+1}\)来转移。这样的转移之前的边就可以任选。\(g\)的转移强制包含最大的点即可不算重。复杂度降到了\(O(3^nn^2)\)。然后获得了和暴力同分的好成绩
发现瓶颈在于数两个点集之间的边数,容易位运算优化,做到\(O(3^nn)\)。
理论上这东西其实是可以跑的,但是常数实在有点大。因此我们考虑\(g\)的转移,有经典优化:每次转移乘上\(-1\),即可将\((-1)^{i+1}\)同步转移,不用记录后面一维。\(dp\)的转移也不用枚举了。常数得到大大优化。
另外其实统计边数还可以用bitset进一步优化至\(O(\frac{M3^n}{w})\),\(w=64\)但没必要。
code:
#include<bits/stdc++.h>
#define I inline
#define ll long long
#define db double
#define lb long db
#define N (15+5)
#define M ((1<<15)+5)
#define K (1500+5)
#define mod 1000000007
#define Mod (mod-1)
#define eps (1e-5)
#define ull unsigned ll
#define it iterator
#define Gc() getchar()
#define Me(x,y) memset(x,y,sizeof(x))
#define Mc(x,y) memcpy(x,y,sizeof(x))
#define d(x,y) ((k+1)*(x)+(y))
#define R(n) (1ll*rand()*rand()%(n)+1)
#define Pc(x) putchar(x)
#define LB lower_bound
#define UB upper_bound
#define PB push_back
using namespace std;
int n,m,k,Fl[N][N],Fs[N],lg[M],H[M];ll Po[N*N],dp[M],g[M],W[M],P[N];
int main(){
freopen("1.in","r",stdin);
int i,j,x,y,z,h;scanf("%d%d",&n,&m);k=(1<<n);for(i=1;i<=m;i++) scanf("%d%d",&x,&y),Fl[x-1][y-1]=1,Fs[x-1]|=(1<<y-1);for(Po[0]=i=1;i<=m;i++) Po[i]=Po[i-1]*2%mod;
for(i=2;i<k;i++) lg[i]=lg[i/2]+1;for(i=1;i<k;i++)H[i]=H[i>>1]+(i&1);for(i=1;i<(1<<n);i++){
z=lg[i];W[i]=W[i^(1<<z)];for(j=0;j<z;j++) i>>j&1&&(W[i]+=Fl[z][j]+Fl[j][z]);
for(j=i&(i-1);j>(i^j);j=(j-1)&i) g[i]=(g[i]+(mod-g[i^j])*(Po[W[j]]-dp[j]+mod))%mod;
for(j=i;j;j=(j-1)&i){
int Ct=0;x=j;while(x) Ct+=H[Fs[lg[x&-x]]&(i^j)],x^=x&-x;
dp[i]=(dp[i]+Po[Ct+W[i^j]]*g[j])%mod;
}
g[i]=(g[i]+Po[W[i]]-dp[i]+mod)%mod;
}printf("%lld\n",(Po[W[k-1]]-dp[k-1]+mod)%mod);
}