「UOJ #37」主旋律

题目

点这里看题目。

分析

一个简单的初始想法是:计算所有最终不是强连通的方案,然后再用总方案减去。

那么非强连通的方案经过缩点后,必然会变成 DAG 的形状。我们可以枚举所有 DAG 的形态,计算方案数:

  • 每个强连通块的方案数:子问题,递归即可;
  • 外部 DAG 的数量;

考虑求解 DAG 的数量。这里假设缩点后图为 \(G'=(V,E')\),且设 \(\operatorname{cnt}_E(S,T)=\sum_{(u,v)\in E}[u\in S\land v\in T]\)

可以设 \(f_S\) 为使得点集 \(S\) 构成 DAG 的边集数量注意到,对于任意的 DAG,必然存在若干个入度为 0 的点,这些点之间不会连边而可以向其余点任意连边,我们可以枚举这些入度为 0 的点:

\[f_S=\sum_{T\subseteq S,T\not=\varnothing} f_{S\setminus T}\times 2^{\operatorname{cnt}_{E'}(T,S\setminus T)} \]

这个转移其实相当于按照拓扑序删除图中的点。

但是,同一层的点会因为子集的多次枚举而导致算重。为了解决这个问题,我们需要容斥:

\[f_S=\sum_{T\subseteq S,T\not=\varnothing}(-1)^{|T|+1}f_{S\setminus T}\times 2^{\operatorname{cnt}_{E'}(T,S\setminus T)} \]

此时我们就得到了一个比较暴力的算法,复杂度应该在 \(O(\sum_k{n\brace k}2^k)\) 左右。


为了加速计算过程,我们需要将原先“缩点后”的图上的 DP,转移到原图上来。

可以发现,原图上一个点集 \(S\) 如果通过某种方法被划分成了 \(t\) 个强连通分量,那么它在“缩点后”的图上 DP 的时候,贡献就是 \((-1)^{t+1}\)。也就是说,它只会和 \(t\) 的奇偶性相关。

这样不难导出一种状态设计:设 \(g_S\) 表示原图上使得 \(S\) 点集为强连通的边集数量,\(h_{0/1,S}\) 表示原图上使得 \(S\) 点集变为偶数个/奇数个点集的边集数量。相似地,我们仍然可以用总方案数减去 DAG 数;计算 DAG 数则枚举最终 DAG 上入度为 0 的点集,算上容斥系数:

\[g_S=2^{\operatorname{cnt}_E(S,S)}-\sum_{T\subseteq S, T\not=\varnothing}(h_{1,T}-h_{0,T})\times 2^{\operatorname{cnt}_E(T,S\setminus T)}\times 2^{\operatorname{cnt}_E(S\setminus T,S\setminus T)} \]

\(h\) 的转移则比较显然:

\[h_{0,S}=\sum_{T\subseteq S,T\not=\varnothing}g_T\times h_{1,S\setminus T}\\ h_{1,S}=\sum_{T\subseteq S,T\not=\varnothing}g_T\times h_{0,S\setminus T} \]

需要注意的是,在转移的时候,\(h_{1,S}\) 里面不应该计入 \(g_S\),这样不会形成 DAG。这一点分析可以帮助我们规避环形转移。

此外,\(\operatorname{cnt}_E\) 需要精细实现一下,比如使用 bitset 。最终复杂度可以做到 \(O(\frac{3^nm}{\omega})\)

小结:

  1. 先思考一些暴力,再尝试优化的思路;
  2. 通过分析最终的形态,入手计算方案数,这是很常见的思路,从结果的特征来入手
  3. 抓住 DAG 的常用特征之一——拓扑序
  4. 从“缩点后”的图转移到原图的时候,需要理清楚哪些是可以直接套用的,同时有必要丢弃一些不重要的信息

代码

#include <bitset>
#include <cstdio>
#include <iostream>

#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )

const int mod = 1e9 + 7;
const int MAXN = 20, MAXM = 215, MAXS = ( 1 << 15 ) + 5;

template<typename _T>
void read( _T &x )/*{{{*/
{
	x = 0; char s = getchar(); int f = 1;
	while( ! ( '0' <= s && s <= '9' ) ) { f = 1; if( s == '-' ) f = -1; s = getchar(); }
	while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
	x *= f;
}/*}}}*/

template<typename _T>
void write( _T x )/*{{{*/
{
	if( x < 0 ) putchar( '-' ), x = -x;
	if( 9 < x ) write( x / 10 );
	putchar( x % 10 + '0' );
}/*}}}*/

std :: bitset<MAXM> out[MAXS], in[MAXS];

int grp[MAXS];
int f[MAXS], g[2][MAXS];
int pw[MAXM];

int N, M;

inline int Mul( int x, int v ) { return 1ll * x * v % mod; }
inline int Sub( int x, int v ) { return ( x -= v ) < 0 ? x + mod : x; }
inline int Add( int x, int v ) { return ( x += v ) >= mod ? x - mod : x; }

int Count( const int S, const int T )/*{{{*/
{
	return ( out[S] & in[T] ).count();
}/*}}}*/

int main()
{
	read( N ), read( M ), pw[0] = 1;
	rep( i, 1, M ) 
	{
		pw[i] = Mul( pw[i - 1], 2 );
		int u, v; read( u ), read( v ), u --, v --;
		for( int S = 0 ; S < ( 1 << N ) ; S ++ )
		{
			if( S >> u & 1 ) out[S].set( i - 1 );
			else out[S].reset( i - 1 );
			if( S >> v & 1 ) in[S].set( i - 1 );
			else in[S].reset( i - 1 );
		}
	}
	for( int S = 0 ; S < ( 1 << N ) ; S ++ ) grp[S] = Count( S, S );
	f[0] = g[0][0] = 1; 
	for( int S = 1 ; S < ( 1 << N ) ; S ++ )
	{
		f[S] = pw[grp[S]]; int low = S & ( - S );
		for( int T = ( S - 1 ) & S ; T ; T = ( T - 1 ) & S )
		{
			if( ! ( T & low ) ) continue;
			g[0][S] = Add( g[0][S], Mul( g[1][S ^ T], f[T] ) );
			g[1][S] = Add( g[1][S], Mul( g[0][S ^ T], f[T] ) );
		}
		for( int T = S ; T ; T = ( T - 1 ) & S )
			f[S] = Sub( f[S], Mul( pw[Count( T, S ^ T ) + grp[S ^ T]], Sub( g[1][T], g[0][T] ) ) );
		g[1][S] = Add( g[1][S], f[S] );
	}
	write( f[( 1 << N ) - 1] ), putchar( '\n' );
	return 0;
}
posted @ 2021-08-09 22:08  crashed  阅读(95)  评论(0编辑  收藏  举报