题意
给你有向图,问有多少个强联通子图
分析
经典的计数问题啊,做第二遍了
首先枚举入度为0的强联通内的点,然后进行容斥
这里的容斥,简单解释一下,假设我们的图不是强联通的,那么缩点之后肯定是一个DAG图
对于一个DAG图,枚举入度为0的点,假设两个点u,v都是入度为0的图,那么枚举到u的时候,v会被算一次,枚举v的时候,u会被算一次,这样奇数次就是-1,偶数次就是+1
对于一个强联通分量,我们枚举缩点后入度为0的强联通分量,(当然了,s-t中可能也有入度为0的强联通分量,才需要容斥)然后奇数个就-1,偶数个就+1
gs g s 就是表示,对于s集合里面的点,分成强联通分量,其实就是上诉说的容斥
对于t=s的时候, gs g s 不用加上 fs f s ,因为这个时候已经是整个子集强联通的
然后就是 O(3n) O ( 3 n ) 的dp了,这个要仔细分析一波才行
代码
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#define pb push_back
using namespace std;
typedef long long ll;
const ll Mod = 1e9+7;
inline ll read()
{
ll p=0; ll f=1; char ch=getchar();
while(ch<'0' || ch>'9'){if(ch=='-') f=-1; ch=getchar();}
while(ch>='0' && ch<='9'){p=p*10+ch-'0'; ch=getchar();}
return p*f;
}
ll mp[16]; ll cnt[1<<16]; ll f[1<<16],g[1<<16],h[1<<16]; ll bin[16*16];
ll low_bit(ll x){return x&(-x);} ll w[1<<16]; vector<ll>v;
void upd(ll &x,ll y){x=(x+y+Mod)%Mod;}
int main()
{
ll n = read(); ll m = read();
bin[0] = 1; for(ll i=1;i<=n*n;i++) bin[i] = bin[i-1] * 2 % Mod;
for(ll i=1;i<=m;i++){ll x = read(); ll y = read(); x--; y--; mp[x] |= bin[y];}
for(ll i=0;i<bin[n];i++) cnt[i] = __builtin_popcount(i);
for(ll i=1;i<bin[n];i++)
{
if(low_bit(i) == i){f[i] = 1; g[i] = -1; continue;}
v.clear(); for(ll j=i;j;j=(j-1)&i) v.pb(j);
for(ll j=0;j<n;j++) if(i&bin[j]) w[bin[j]] = cnt[mp[j] & i];
w[0] = 0; for(ll j=v.size()-1;j>=0;j--) w[v[j]] = w[v[j]^low_bit(v[j])] + w[low_bit(v[j])];
f[i] = bin[w[i]];
for(ll j=1;j<v.size();j++) if(v[j] & low_bit(i)) upd(g[i],- f[v[j]] * g[i^v[j]] % Mod);
// for(ll j=i;j;j=(j-1)&i) printf("%lld %lld %lld\n",i,j,g[j]);
for(ll j=0;j<v.size();j++)
{
upd(f[i] , bin[w[i^v[j]]] * g[v[j]] % Mod);
//printf("%lld %lld\n",i,v[j]);
}
upd(g[i],-f[i]);
for(ll j=0;j<v.size();j++) w[v[j]] = 0;
}
return printf("%lld\n",f[bin[n]-1]),0;
}