UOJ #37. 【清华集训2014】主旋律 整理--zhengjun

发布时间 2023-07-22 11:57:25作者: A_zjzj

好像没做过 DAG 计数的题。

首先看到数据范围,考虑状压。

方便起见,记 \(cnt_{S,T}=\sum\limits_{(u,v)\in E}[u\in S \and v \in T]\)

\(f_S\) 表示 \(S\) 为强连通分量的选边方案数,由于正面很难算。

考虑反面:

\[f_S=2^{cnt_{S,S}}-\sum\limits_{\varnothing\subsetneqq T \subseteq S}g_T\times 2^{cnt_{S\backslash T,S}}\\ g_S=\sum\limits_{\varnothing \subsetneqq T \subseteq S}g_{S\backslash T}\times (-f_T) \]

初始:

\[f_{\varnothing}=0,g_{\varnothing}=-1 \]

这里使用了一个容斥,意思是枚举 \(S\) 的子集 \(T\) 作为出度为 \(0\) 的若干强连通分量的并集。

那么剩下的 \(S\backslash T\) 连出去的边就可以任意选或不选。

但是这样会算重,因为考虑一种选边方案使得出度为 \(0\) 的强连通分量为 \(T_1,T_2,\cdots,T_k\)

那么这种方案会在任意 \(\varnothing \subsetneqq P\subseteq\{1,2,\cdots k\}\) 的时候被 \(T=\bigcup\limits_{i\in P}T_i\) 计算一次。

由于 \(1=\sum\limits_{\varnothing \subsetneqq P\subseteq\{1,2,\cdots k\}}(-1)^{|P|-1}\),所以加上这个容斥系数就能做到恰好算到一次。

于是 \(g_{\varnothing}=-1\),转移时 \(g\) 乘上 \(f\) 时会多乘一个 \(-1\)

注意转移顺序,应该先转移 \(g\),再转移 \(f\),然后等 \(f_S\) 计算好了,再加到 \(g_S\) 上去。

因为计算 \(f_S\) 时所需的 \(g_S\) 是至少两个小集合并起来的,不能算上自己。

另外 \(cnt_{S,S}\) 可以预处理,\(cnt_{S\backslash T,S}\) 可以在每个 \(S\) 的时候枚举 \(T\) 计算一次。

时间复杂度:\(O(3^n\log \log)\),瓶颈在于计算 popcount。

代码

#include<bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=15,M=1<<N,E=N*N,mod=1e9+7;
int n,m,U,a[N][N];
int to[N],cnt[M],f[M],g[M],h[M],pw[E];
int main(){
	freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	cin>>n>>m,U=(1<<n)-1;
	for(int i=pw[0]=1;i<=m;i++)pw[i]=pw[i-1]*2%mod;
	for(int u,v;m--;){
		cin>>u>>v,a[--u][--v]=1;
	}
	for(int u=0;u<n;u++){
		for(int v=0;v<n;v++)to[u]|=a[u][v]<<v;
	}
	for(int S=0;S<=U;S++){
		int i=__builtin_ctz(S);
		cnt[S]=cnt[S^(S&-S)];
		for(int j=0;j<n;j++)if(S>>j&1)cnt[S]+=a[i][j]+a[j][i];
	}
	g[0]=mod-1;
	for(int S=1;S<=U;S++){
		for(int T=S;T;--T&=S)if(T&(S&-S)){
			g[S]=(g[S]+1ll*g[S^T]*(mod-f[T]))%mod;
		}
		h[S]=0;
		for(int T=S;T;--T&=S){
			if(T<S){
				int i=__builtin_ctz(S^T);
				h[T]=h[T^(1<<i)]+__builtin_popcount(to[i]&S);
			}
			f[S]=(f[S]+1ll*g[T]*pw[h[T]])%mod;
		}
		f[S]=(pw[cnt[S]]-f[S]+mod)%mod;
		g[S]=(g[S]+f[S])%mod;
	}
	cout<<f[U];
	return 0;
}