「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}\times 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\) 个状态,那么通过隔板法计算得到方案数为 \(\binom{g_{i,j}+k-1}{g_{i,j}-1}\)

做二维的卷积复杂度为 \(O(n^4)\),再加上枚举 \(k\) 的调和级数过程,总复杂度就是 \(O(n^4\ln 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( '\n' );
	return 0;
}
posted @ 2021-10-12 16:55  crashed  阅读(46)  评论(0编辑  收藏  举报