[BZOJ3812]主旋律
[BZOJ3812]主旋律
Description
响应主旋律的号召,大家决定让这个班级充满爱,现在班级里面有 n 个男生。如果 a 爱着 b,那么就相当于 a 和 b 之间有一条 a→b 的有向边。如果这 n 个点的图是强联通的,那么就认为这个班级是充满爱的。不幸的是,有一些不好的事情发生了,现在每一条边都可能被摧毁。我作为爱的使者,想知道有多少种摧毁的方式,使得这个班级任然充满爱呢?(说人话就是有多少边的子集删去之后整个图仍然强联通。)
Input
第一行两个数 n 和 m,表示班级里的男生数和爱的关系数。接下来 m 行,每行两个数 a 和 b,表示男生 a 爱着男生 b。同时 a 不等于 b。所有男生从 1 到 n 标号。同一条边不会出现两遍,但可能出现 a 爱着 b,b 也爱着 a 的情况,这是两条不同的边。
Output
输出一行一个整数,表示对 \(10^9+7\) 取模后的答案。
Sample Input
5 15
4 3
4 2
2 5
2 1
1 2
5 1
3 2
4 1
1 4
5 4
3 4
5 3
2 3
1 5
3 1
Sample Output
9390
HINT
对于 100% 的数据满足: \(n\leq 15,0\leq m\leq n\)。
试题分析
首先考虑\(f_i\)表示\(i\)点集的强连通图个数,但是发现这样不止需要添加一个点去找其与另外一个集合的连边,还需要考虑构成环的情况。
那么我们可以反向考虑\(f_i\)表示\(i\)点集强连通图的个数,\(non_i\)表示\(i\)点集非强联通图个数。
由此可知,\(f_i=2^{s_i}-non_i\),其中\(s_i\)表示i点集中的边数,只需要关心\(non_i\)了。
这里尝试着从答案性质的角度进行状态的定义,发现最后的 不合法的 答案一定是一个缩点之后的拓扑图,也就是说必定存在出度和入度为0的强连通分量,且强连通分量个数不为1。
因为定义出度或入度为0都是一样的,我们这里不妨考虑存在入度为0且有多个强连通分量的图的个数。
设\(g_i\)表示点集为i的满足如上条件图的个数,那么直观的方程就是\(g_i=\sum_{j\subset i} g_{i-j}\times f_j\),边数的话我们在f转移时考虑。
到这里就出现了一个问题,\(g_{i-j}\)中也会包含入度为0的强连通分量,会重复计算。
那么只需要奇偶容斥一下,令\(g_i=-\sum_{j\subset i} g_{i-j}\times f_j\),于是得到\(f_i=2^{s_i}-\sum_{j\subset i} 2^{s_i-e_{i,j}} g_j\)
转移即可。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#define LL long long
inline LL read(){
LL x=0,f=1; char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
return x*f;
}
const LL INF = 2147483600;
const LL MAXN = 100010;
const LL Mod = 1000000007LL;
LL n,m;LL cnt[MAXN+1]; LL Ins[MAXN+1],out[MAXN+1],bin[MAXN+1],in[MAXN+1],s[MAXN+1];
LL g[MAXN+1],f[MAXN+1];
inline void calc(LL now,LL i){
if((now-1)&i) calc((now-1)&i,i);
Ins[now]=Ins[now-(now&(-now))]+cnt[in[now&(-now)]&i];
}
int main(){
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
n=read(),m=read(); bin[0]=1;
for(LL i=1;i<=m;i++){
LL u=read(),v=read();
in[1<<(v-1)]=in[1<<(v-1)]|(1<<(u-1));
out[1<<(u-1)]=out[1<<(u-1)]|(1<<(v-1));
bin[i]=bin[i-1]*2LL%Mod;
} for(LL i=1;i<(1<<n);i++) cnt[i]=cnt[i-(i&(-i))]+1;
for(LL i=1;i<(1<<n);i++){
calc(i,i); s[i]=s[i-(i&(-i))]+cnt[out[i&(-i)]&i]+cnt[in[i&(-i)]&i]; f[i]=bin[s[i]];
for(LL j=i-(i&(-i));j;j=(j-1)&(i-(i&-i))) g[i]=(g[i]-f[i^j]*g[j]%Mod+Mod)%Mod;
for(LL j=i;j;j=(j-1)&i) f[i]=(f[i]-bin[s[i]-Ins[j]]*g[j]%Mod+Mod)%Mod;
g[i]=(g[i]+f[i])%Mod;
} printf("%lld\n",f[(1<<n)-1]);
return 0;
}