[CSP2019] Emiya 家今天的饭

题目

点这里看题目。

分析

感觉比往年的 NOIP 的 D2T1 更难。不过看看 D1T3 也就觉得挺合理了。

32pts

暴力搜索不多说,时间 \(O(m(m+1)^n)\) ,其中的 \(O(m)\) 用于检查。

64pts

这是考场上的思路,想了大概 10 min 不到。

针对 \(m\) 很小的数据,直接把 \(m\) 种食材的使用次数全部塞到状态里面。以 \(m=2\) 为例子,可以有如下状态:

\(f(i,j,k)\):前 \(i\) 种烹饪方法,用了 \(j\) 次第一种食材, \(k\) 次第二种食材的方案数。

转移很显然:

\[f(i,j,k)=f(i-1,j,k)+a_{i,1}\times f(i-1,j-1,k)+a_{i,2}\times f(i-1,j,k-1) \]

最后统计一下答案,应该长这个样子:

\[\sum_{k=1}^n\sum_{j=1}^{\lfloor\frac k 2\rfloor} f(n,j,k-j)[k-j\le \lfloor\frac k 2\rfloor] \]

时间复杂度为 \(O(n^{m+1})\)

84pts

考场上想容斥的时候搞忘了\(\le \lfloor\frac k 2\rfloor\)的条件,直接导致暴毙

32pts 和 64pts 都是正向思考,尝试满足条件,但是我们似乎已经走到了尽头。

很自然地,我们考虑逆向思考,也就是容斥,考虑不满足条件。

题目有用的限制也就这么一个:

\[\text{每种主要食材出现次数}\le\lfloor\frac{k}{2}\rfloor \]

我们就反其道而行之,考虑,如果一种主要食材出现次数 \(>\lfloor\frac k 2\rfloor\) 会发生什么?

这就意味着,其它主要食材出现总次数\(\le \lfloor\frac k 2\rfloor\),也就意味着,最多只会有一种食材不满足限制,我们可以枚举不满足限制的食材进行容斥。

上述性质顺便也说明了,钦定不合法食材的数量一定大于其他食材的总数,这就便于设计状态进行 DP :

\(f(i,j,k)\):前 \(i\) 种烹饪方法,使用了 \(j\) 次钦定食材, \(k\) 次其他食材的方案数。

设钦定食材编号为 \(u\)\(s_i\) 为使用第 \(i\) 种烹饪方法能做出的菜的总数。

可以类比 64pts 给出的转移方程:

\[f(i,j,k)=f(i-1,j,k)+a_{i,u}\times f(i-1,j-1,k)+(s_i-a_{i,u})\times f(i-1,j,k-1) \]

这样做是 \(O(n^3m)\) 的。

100pts

上述方法会 T 掉后 4 个大点,还需优化。

我们感觉已经很接近正解了。外层容斥枚举无法优化,因而考虑优化内层的 DP 。

我们想要知道的只是数量关系,并不想要实际数量。而数量关系可以通过运算与运算结果来表达。

典型的例子:\(j>k\Leftrightarrow j-k>0\)

利用这样的性质,我们可以优化状态:

\(g(i,j)\):前 \(i\) 种烹饪方法,钦定食材比其他食材多用 \(j\)的方案数。

转移也很简单:

\[g(i,j)=g(i-1,j)+a_{i,u}\times g(i-1,j-1)+(s_i-a_{i,u})\times g(i-1,j+1) \]

需要注意 \(j\) 可以取到负数

时间复杂度是 \(O(n^2m)\),足以通过本题。

代码

32pts

int cnt[MAXM];
int a[MAXN][MAXM];
LL ans; int N, M;

void DFS( const int ind, const LL way, const int tot )
{
	if( ! way ) return ;
	if( ind > N ) 
	{
		for( int i = 1 ; i <= M ; i ++ )
			if( cnt[i] > tot >> 1 )
				return ;
		( ans += way ) %= mod;
		return ;
	}
	DFS( ind + 1, way, tot );
	for( int i = 1 ; i <= M ; i ++ )
	{
		cnt[i] ++; 
		DFS( ind + 1, 1ll * way * a[ind][i] % mod, tot + 1 ); 
		cnt[i] --;
	}
}

64pts

这里以 \(m=2\) 的情况为例, \(m=3\) 的请自行斟酌。

    f[0][0] = 1;
    for( int i = 1 ; i <= N ; i ++ )
        for( int j = i ; ~ j ; j -- )
            for( int k = i - j ; ~ k ; k -- )
            {
                if( j ) add( f[j][k], 1ll * f[j - 1][k] * a[i][1] % mod );
                if( k ) add( f[j][k], 1ll * f[j][k - 1] * a[i][2] % mod );
            }
    int ans = 0;
    for( int k = 1 ; k <= N ; k ++ )
        for( int j = 1 ; j <= ( k >> 1 ) ; j ++ )
            if( k - j <= ( k >> 1 ) )
                add( ans, f[j][k - j] );

84pts

int f[MAXM][MAXM];
int a[MAXN][MAXM], s[MAXN];
int N, M;

void add( int &x, const int v ) { x = ( x + v >= mod ? x + v - mod : x + v ); }

int calc( const int imp )
{
	for( int j = 0 ; j <= N ; j ++ )
		for( int k = 0 ; k <= N ; k ++ )
			f[j][k] = 0;
	f[0][0] = 1;
	for( int i = 1 ; i <= N ; i ++ )
		for( int j = i ; ~ j ; j -- )
			for( int k = i - j ; ~ k ; k -- )
			{
				if( j ) add( f[j][k], 1ll * f[j - 1][k] * a[i][imp] % mod );
				if( k ) add( f[j][k], 1ll * f[j][k - 1] * ( s[i] - a[i][imp] ) % mod ); 
			}
	int ret = 0;
	for( int k = 1 ; k <= N ; k ++ )
		for( int j = ( k >> 1 ) + 1 ; j <= k ; j ++ )
			add( ret, f[j][k - j] );
	return ret;
}

int main()
{
	int all = 1;
	read( N ), read( M );
	for( int i = 1 ; i <= N ; i ++ )
	{
		for( int j = 1 ; j <= M ; j ++ )
			read( a[i][j] ), add( s[i], a[i][j] );
		all = 1ll * all * ( s[i] + 1 ) % mod;
	}
	all = ( all - 1 + mod ) % mod;
	for( int i = 1 ; i <= M ; i ++ ) 
		all = ( all - calc( i ) + mod ) % mod;
	write( all ), putchar( '\n' );
	return 0;
}

100pts

int& F( const int i, const int j ) { return f[i][j + N]; }
void add( int &x, const int v ) { x = ( x + v >= mod ? x + v - mod : x + v ); }

int calc( const int imp )
{
	for( int i = 0 ; i <= N ; i ++ )
		for( int j = -N ; j <= N ; j ++ )
			F( i, j ) = 0;
	F( 0, 0 ) = 1;
	for( int i = 1 ; i <= N ; i ++ )
		for( int j = -i ; j <= i ; j ++ )
		{
			F( i, j ) = F( i - 1, j );
			if( j > -i ) add( F( i, j ), 1ll * a[i][imp] * F( i - 1, j - 1 ) % mod );
			if( j < i ) add( F( i, j ), 1ll * ( s[i] - a[i][imp] + mod ) * F( i - 1, j + 1 ) % mod );
		}
	int ret = 0;
	for( int j = 1 ; j <= N ; j ++ )
		add( ret, F( N, j ) );
	return ret;
}
posted @ 2020-07-24 16:59  crashed  阅读(159)  评论(0编辑  收藏  举报