题解 BZOJ3812【[清华集训 2014] 主旋律】
problem
给定一个 \(n\) 个点的有向图,问这个图有多少个强连通子图。保证 \(n\leq 15\)。
preparing
一个有向图有 \(n\) 个点,求它的 DAG 子图数量。\(n\leq 15\)。
解:令 \(f[S]\) 表示点集 \(S\) 有多少个 DAG 子图。枚举这个 DAG 中入度为 0 的部分,然后递归下去。由于不能一次枚举完所有入度为 0 的点,我们对着这些点容斥。
\[f[S]=\sum_{T\subseteq S,T\neq\varnothing}(-1)^{|T|+1}2^{ways(T,S-T)}f[S-T].
\]
solution
一个非强连通的子图,缩点后是一个至少两个点的 DAG(这个 DAG 可以不连通)。
对于点集 \(S\),考虑强连通子图 = 所有图 - 非强连通子图。
令强连通子图的数量为 \(f[S]\),枚举 \(T\subset S\),使 \(T\) 是一个强连通分量,\(S-T\) 就随意,而且强制规定 \(T\) 在缩点图的入度为零。
\[f[S]=all[S]-\sum_{T\subset S}2^{ways(T,S-T)}f[T]all[S-T].
\]
- \(all[S]\) 表示所有图的数量。
- \(ways(S,T)\) 表示 \(\sum_{u\in S,v\in T}g[u][v]\)。注意到 \(S\cap T=\varnothing\),它的复杂度顶满是 \(O(3^nn^2)\)。
错误的。考虑 \(1\to 3,2\to 3\) 会被算两次。
考虑问题在哪里呢?枚举 \(T\) 的时候,我们没有钦定 \(T\) 一定是所有的入度为 \(0\) 的点。我们对着 \(T\) 容斥:
- 将 \(T\) 划分为若干个强连通分量,奇数个的贡献是 1,偶数个的贡献是 -1;
- 令 \(g[T]\) 表示所有划分方案的和。
将 \(f[T]\) 换成 \(g[T]\) 就行了。
\[f[S]=all[S]-\sum_{T\subset S}2^{ways(T,S-T)}g[T]all[S-T].
\]
怎么算 \(g[S]\)?
- 枚举一个 \(T\subset S\),然后划分成 \(-f[T]g[S-T]\)。
- 细节一:一种好的做法是令 \(g[0]=0\),然后特判 \(T\) 是整一块强连通分量的情况。
- 细节二:为了避免判重等奇奇怪怪的问题,我们钦定一个 \(x\in S\),然后枚举 \(x\in T\land T\subset S\)。
\[g[S]=f[S]-\sum_{x\in T\subset S}f[T]g[S-T].
\]
code
细节巨大
点击查看代码
debug:
2 1
1 2
//输出 0
先算 \(h\)(忽略单独块的影响),然后 \(f\) 枚举全部,最后直接 \(h+=f\)。
理解:少掉的这个 \(h\),它就只是单纯的容斥,把空图干掉,除此之外没有实际意义。
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr,##__VA_ARGS__)
#else
#define debug(...) void(0)
#endif
typedef long long LL;
const int P=1e9+7;
LL mod(LL x){return (x%P+P)%P;}
void red(LL &x){x=mod(x);}
int lowbit(int S){return S&-S;}
int getbit(int k){return 1<<k;}
bool conbit(int S,int k){return S&getbit(k);}
int n,m,g[15][15],lg[1<<15];
LL f[1<<15],h[1<<15],all[1<<15],pow2[100010];
int calc(int S,int T){//S\cap T=\varnothing
int ans=0;
// debug("S=%d,T=%d\n",S,T);
for(int i=S;i;i-=lowbit(i)){
for(int j=T;j;j-=lowbit(j)){
// debug("ans+=g[%d][%d]\n",lg[lowbit(i)],lg[lowbit(j)]);
ans+=g[lg[lowbit(i)]][lg[lowbit(j)]];
}
}
return ans;
}
LL dp(){
memset(f,0,sizeof f);
memset(h,0,sizeof h);
all[0]=0;
for(int S=1;S<1<<n;S++){
red(all[S]=all[S^lowbit(S)]+calc(lowbit(S),S^lowbit(S))+calc(S^lowbit(S),lowbit(S)));
for(int St=S^lowbit(S),T=St;;T=(T-1)&St){red(h[S]-=f[T|lowbit(S)]*h[St^T]);debug("T=%d,h[%d]-=f[%d]*h[%d](%d*%d)\n",T,S,T|lowbit(S),St^T,f[T|lowbit(S)],h[St^T]);if(!T) break;}
debug("calc: h[%d]=%lld\n",S,h[S]);
f[S]=pow2[all[S]];
for(int T=S;T;T=(T-1)&S) red(f[S]-=pow2[calc(T,S^T)+all[S^T]]*h[T]),debug("T=%d,f[%d]-=%lld*%lld\n",T,S,pow2[calc(T,S^T)+all[S^T]],h[T]);
red(h[S]+=f[S]);
debug("all[%d]=%lld,f[%d]=%lld,h[%d]=%lld\n",S,all[S],S,f[S],S,h[S]);
}
return f[getbit(n)-1];
}
int main(){
// #ifdef LOCAL
// freopen("input.in","r",stdin);
// #endif
for(int i=pow2[0]=1;i<=1e5;i++) red(pow2[i]=pow2[i-1]*2);
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++) lg[getbit(i)]=i;
for(int i=1,u,v;i<=m;i++) scanf("%d%d",&u,&v),g[u-1][v-1]=1;
printf("%lld\n",dp());
return 0;
}
/*
calc: h[3]=-1+x
f[3]=2-3-h[3]=-1+1-x
*/
本文来自博客园,作者:caijianhong,转载请注明原文链接:https://www.cnblogs.com/caijianhong/p/solution-bzoj3812.html