zoukankan      html  css  js  c++  java
  • 「CF848D」Shake it!

    题目

    点这里看题目。

    分析

    很不错的 DP 题目。

    简单分析一下问题的结构:对于一次操作,直观上我们可以选一条边,然后加入一个类三角形的结构。现在对于这个三角形,我们既可以基于初始的边继续加入三角形,也可以基于新的三角形的另外两边加入新的三角形。注意到,一个三角形的另外两条边对应的是独立的子问题。这个结构诱导我们通过 DP 来解决这个问题。

    再稍微思考一下,对于初始的边而言,会有若干个三角形基于它生成。这些三角形彼此独立。这引导我们可以单独拿出一个三角形来考虑:设 (f_{i,j}) 表示经过了 (i) 次操作后,最小割为 (j) 的本质不同的图的个数,而 (g_{i,j}) 表示经过 (i) 次操作后,最小割为 (j)仅有一个三角形基于初始边生成的图的个数。

    先考虑 (g) 的转移。不妨设从 ((s,t)) 生成了 ((s,w))((w,t)),那么 (g) 的最小割对应的是 ((s,w))((w,t)) 两个子问题的最小割的较小值。因此,我们很容易写出有关贡献的式子:

    [g_{a+b+1,min{p,q}}leftarrow f_{a,p} imes f_{b,q} ]

    这个转移中,第一维做的是加法卷积,第二维做的是 (min) 卷积。将第二维做一个后缀和就可以方便地计算它了。

    再考虑 (f)。我们实际上是要从 (g) 里面选出若干个本质不同的状态 ((i_1,j_1),(i_2,j_2),dots,(i_k,j_k)) 来组合得到 (f_{i,j})。由于 ((s,t)) 这条边本身也会贡献 1 的流量,因此要求这些状态满足 (sum_{l=1}^ki_l=i)(sum_{l=1}^kj_l=j-1)。两维做的都是加法卷积。

    接着再来考虑组合的方式。为了避免算重,我们需要保证若干个状态是无序的,常用的方法就是直接有序地枚举状态并尝试加入。那么我们按照计算 (g) 的顺序进行贡献就好了。

    再考虑每个状态选择的方案数。如果两个状态来自不同的 (g),就不用管了。否则,我们只需要保证同样的状态之间没有顺序即可,也就是可重地选状态。如果要从 (g_{i,j}) 里面选出 (k) 个状态,那么通过隔板法计算得到方案数为 (inom{g_{i,j}+k-1}{g_{i,j}-1})

    做二维的卷积复杂度为 (O(n^4)),再加上枚举 (k) 的调和级数过程,总复杂度就是 (O(n^4ln n)) 的。

    实现需要注意:对于 (f),我们需要维护它的在线卷积。说白了就是每次算出来一个新的 (g) 都需要对 (f) 贡献。

    小结:

    1. 仔细观察问题,多找一些明确、重要的结构、子结构;
    2. 关注一下从 (g) 组合到 (f) 的过程。这里用到的方法在无标号计数问题中都比较常用,记一下。

    代码

    #include <cstdio>
    
    #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 = 55;
    
    template<typename _T>
    void read( _T &x )
    {
    	x = 0; char s = getchar(); bool f = false;
    	while( s < '0' || '9' < s ) { f = s == '-', s = getchar(); }
    	while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
    	if( f ) x = - x;
    }
    
    template<typename _T>
    void write( _T x )
    {
    	if( x < 0 ) putchar( '-' ), x = -x;
    	if( 9 < x ) write( x / 10 );
    	putchar( x % 10 + '0' );
    }
    
    int f[MAXN][MAXN], g[MAXN][MAXN], h[MAXN][MAXN];
    int F[MAXN][MAXN], G[MAXN][MAXN];
    
    int fac[MAXN], ifac[MAXN];
    
    int N, M;
    
    inline int Qkpow( int, int );
    inline int Mul( int x, int v ) { return 1ll * x * v % mod; }
    inline int Inv( const int a ) { return Qkpow( a, mod - 2 ); }
    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; }
    inline void Upt( int &x, const int v ) { x = Add( x, v ); }
    
    void Init( const int n )
    {
    	fac[0] = 1; rep( i, 1, n ) fac[i] = Mul( fac[i - 1], i );
    	ifac[n] = Inv( fac[n] ); per( i, n - 1, 0 ) ifac[i] = Mul( ifac[i + 1], i + 1 );
    }
    
    inline int Qkpow( int base, int indx )
    {
    	int ret = 1;
    	while( indx )
    	{
    		if( indx & 1 ) ret = Mul( ret, base );
    		base = Mul( base, base ), indx >>= 1;
    	}
    	return ret;
    }
    
    int main()
    {
    	read( N ), read( M );
    	if( N + 1 < M ) return puts( "0" ), 0;
    	F[0][1] = f[0][1] = 1, h[0][0] = 1, Init( N );
    	rep( i, 1, N )
    	{
    		per( j, N + 1, 1 )
    		{
    			G[i][j] = 0;
    			rep( k, 0, i - 1 )
    				Upt( G[i][j], Mul( F[k][j], F[i - k - 1][j] ) );
    			g[i][j] = Sub( G[i][j], G[i][j + 1] );
    			per( p, N, 0 ) per( q, N + 1, 0 )
    			{
    				int tmp = 0;
    				for( int k = 0, up = 1 ; i * k <= p && j * k <= q ; k ++ )
    					Upt( tmp, Mul( h[p - i * k][q - j * k], Mul( ifac[k], up ) ) ),
    					up = Mul( up, Add( g[i][j], k ) );
    				h[p][q] = tmp;
    			}
    		}
    		per( j, N + 1, 1 ) F[i][j] = Add( F[i][j + 1], f[i][j] = h[i][j - 1] );
    	}
    	write( f[N][M] ), putchar( '
    ' );
    	return 0;
    }
    
  • 相关阅读:
    App分享微信小程序
    PHP-FFMpeg 操作视频/音频文件 (转)
    用户画像
    phpcms中的RBAC权限系统
    PHPExcel生成excel
    OPNET中节点模型中包流的索引号的含义
    删除opnet之前保存或打开的目录后每次打开总会提示warning
    opnet 的学习方法有感
    win10:两款轻量级美化软件使用技巧(StartlsBack++与RocketDock)
    任务栏透明
  • 原文地址:https://www.cnblogs.com/crashed/p/15398610.html
Copyright © 2011-2022 走看看