【清华集训2014】主旋律
【清华集训2014】主旋律
题目大意
给定一张\(n\)个点\(m\)条边的无向图,保证该图整个图为一个强联通分量,保证无重边自环。
现在需要求出:有多少种删边方案,使得删完边后,整个图依旧是一个强联通分量。
数据范围:\(n\leq 15 , m\leq n(n-1)\) 。
题解
容斥题是真的鸡贼......,这\(TMD\)是人能想出来的?
希望下次自己再来看这篇题解的时候还能看懂吧......
规定\(E[U,V]\)表示出度在\(U\)中,入度在\(V\)中的所有边的集合,其中\(U\)、\(V\)为点集。
对于一个点集\(S\),我们随便连边,然后缩点。
那么缩完点后,形成的一定是一个\(DAG\)。
我们枚举缩完点后,构成出度为\(0\)的\(scc\)的点是哪些,设这个点集为\(T\)。
那么每一种\(T\)集合都可以与原来的某些连边方案对应起来。
我们确定完\(T\)后,\(S-T\)中的点先让它随便连,然后\(S-T\)连向\(T\)中的边也可以随便连。
我们可以暂时得到一个式子:
设\(f_S\)表示连完\(E[S,S]\)中的边后,\(S\)整张图为一个强联通分量的方案数,即题目所求。
上式中\(g_T\)表示把\(T\)中的点连成若干强联通分量的方案数。
显然,直接\(g_T = \sum_{s_1,s_2...,s_k} \prod_{k} f_s\)是不对的。
因为由于\(E[S-T,S-T]\)中的边也是随便连的,所以还有可能会形成其他的出度为\(0\)的\(scc\)。
所以这里应该是存在一个容斥系数的,即:
我们令这个式子为\(g_T\)的定义式。
考虑一下这个容斥系数\(coef\)到底是什么。
我们设\(r_k\)表示划分成\(k\)个出度为\(0\)的\(scc\)的方案数应该要被算几遍,显然\(r_k=1\)。
然后我们来推容斥系数\(coef\)应该设为多少才能够使得计算出来的结果与\(r_k\)等效。
对于被划分成\(k\)个出度为\(0\)的\(scc\)的方案,
它在划分为\(k-1\)个中被算了\(\binom{k}{k-1}\)次,在划分为\(k-2\)个中被算了\(\binom{k}{k-2}\)次......
所以我们有:
显然是一个二项式反演,结合\(r_k=1\)与二项式定理我们可以得到:
所以我们得到了容斥系数\(coef_k = (-1)^{k+1}\),带入之前\(g_T\)的定义式中:
根据容斥系数可以看出,\(g_{null} = -1\)。
现在我们终于弄清楚\(g_T\)究竟应该是什么了!
到此为止,所有的准备工作已经就绪,下面开始求答案。
回到这个式子:
注意到这个式子中是没有\(f\)数组的,所以:
所以顺次把\(g\)数组全部推出来即可。
然后我们来研究一下\(g\)的定义式:
注意到,每加入一个\(scc\),容斥系数会乘上一个\(-1\)。
由于是无序关系,所以我们可以枚举最小编号所在的强联通分量\(s_1\),然后就可以得到:
类似之前求\(g\)的技巧,移项可以得到:
所以说只要解决了\(g\),就可以顺次推出\(f\),而\(g\)我们已经会求了,所以就做完了。
累死我了.......
不玩了不玩了,再以不玩这种毒瘤题了......
实现代码
#include<bits/stdc++.h>
#define IL inline
#define mod 1000000007
using namespace std ;
int lk[20][20],Inside[(1<<17)],Edge[(1<<17)][20],n,m,pw[233333],pos[(1<<17)] ;
int f[(1<<17)] , g[(1<<17)] , num[(1<<17)] , mx ;
IL void Pre() {
for(int i = 1,u,v; i <= m; i ++) cin >> u >> v , lk[u][v] ++ ;
mx = (1 << n) ;
for(int S = 0; S < mx; S ++)
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= n; j ++)
if((S&(1<<(i-1))) && (S&(1<<(j-1)))) Inside[S] += lk[i][j] ;
for(int S = 0; S < mx; S ++)
for(int v = 1; v <= n; v ++) if(S & (1 << (v - 1))) continue ;
else for(int u = 1; u <= n; u ++)
if(S & (1 << (u - 1))) Edge[S][v] += lk[u][v] ;
for(int S = 0; S < mx; S ++) {
for(int v = 1; v <= n; v ++) if(S & (1 << (v - 1))) pos[S] = v ;
}
pw[0] = 1 ;
for(int i = 1; i <= m; i ++) pw[i] = (pw[i - 1] << 1) % mod ; return ;
}
IL void add(int &x , int y) {x += y ; if(x >= mod) x -= mod ; }
int main() {
freopen("scon.in","r",stdin) ;
freopen("scon.out","w",stdout) ;
cin >> n >> m ;
Pre() ;
g[0] = mod - 1 ;
for(int S = 1; S < mx; S ++) {
g[S] = pw[Inside[S]] ;
for(int T = S,tmp; T ; T = (T - 1) & S) {
num[T] = 0 ; tmp = T ; while(tmp) add(num[T] , Edge[S ^ T][pos[tmp]]) , tmp -= (1 << (pos[tmp] - 1)) ;
}
for(int T = S; T ; T = (T - 1) & S)
if(S ^ T) add(g[S] , mod - 1ll * g[T] * pw[Inside[S ^ T]] % mod * pw[num[T]] % mod) ;
}
for(int S = 1; S < mx; S ++) {
for(int s1 = S; s1 ; s1 = (s1 - 1) & S) {
if(s1 & (1 << (pos[S] - 1))) add(f[S] , 1ll * f[s1] * g[S ^ s1] % mod) ;
}
add(f[S] , g[S]) ;
}
cout << f[mx - 1] << endl ; return 0 ;
}