zoukankan      html  css  js  c++  java
  • 「UOJ #37」主旋律

    题目

    点这里看题目。

    分析

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

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

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

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

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

    [f_S=sum_{Tsubseteq S,T ot=varnothing} f_{Ssetminus T} imes 2^{operatorname{cnt}_{E'}(T,Ssetminus T)} ]

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

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

    [f_S=sum_{Tsubseteq S,T ot=varnothing}(-1)^{|T|+1}f_{Ssetminus T} imes 2^{operatorname{cnt}_{E'}(T,Ssetminus T)} ]

    此时我们就得到了一个比较暴力的算法,复杂度应该在 (O(sum_k{nrace 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_{Tsubseteq S, T ot=varnothing}(h_{1,T}-h_{0,T}) imes 2^{operatorname{cnt}_E(T,Ssetminus T)} imes 2^{operatorname{cnt}_E(Ssetminus T,Ssetminus T)} ]

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

    [h_{0,S}=sum_{Tsubseteq S,T ot=varnothing}g_T imes h_{1,Ssetminus T}\ h_{1,S}=sum_{Tsubseteq S,T ot=varnothing}g_T imes h_{0,Ssetminus 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( '
    ' );
    	return 0;
    }
    
  • 相关阅读:
    电脑U盘启动制作
    windows系统使用
    CentOS升级Openssl至openssl-1.1.0
    PHP编译安装时常见错误解决办法
    阿里 Linux服务器外网无法连接MySQL解决方法
    centos 下 sphinx安装和配置
    集成百度编辑器 ueditor 后端配置项没有正常加载,上传插件不能正常使用!
    nginx 环境 thinkphp 隐藏index.php
    在 Linux 下搭建 Git 服务器
    MySQL远程连接不上的解决方法
  • 原文地址:https://www.cnblogs.com/crashed/p/15120928.html
Copyright © 2011-2022 走看看