题目
点这里看题目。
分析
感觉比往年的 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) 次第二种食材的方案数。
转移很显然:
最后统计一下答案,应该长这个样子:
时间复杂度为 (O(n^{m+1})) 。
84pts
考场上想容斥的时候搞忘了(le lfloorfrac k 2
floor)的条件,直接导致暴毙
32pts 和 64pts 都是正向思考,尝试满足条件,但是我们似乎已经走到了尽头。
很自然地,我们考虑逆向思考,也就是容斥,考虑不满足条件。
题目有用的限制也就这么一个:
我们就反其道而行之,考虑,如果一种主要食材出现次数 (>lfloorfrac k 2 floor) 会发生什么?
这就意味着,其它主要食材出现总次数都(le lfloorfrac k 2 floor),也就意味着,最多只会有一种食材不满足限制,我们可以枚举不满足限制的食材进行容斥。
上述性质顺便也说明了,钦定不合法食材的数量一定大于其他食材的总数,这就便于设计状态进行 DP :
(f(i,j,k)):前 (i) 种烹饪方法,使用了 (j) 次钦定食材, (k) 次其他食材的方案数。
设钦定食材编号为 (u) ,(s_i) 为使用第 (i) 种烹饪方法能做出的菜的总数。
可以类比 64pts 给出的转移方程:
这样做是 (O(n^3m)) 的。
100pts
上述方法会 T 掉后 4 个大点,还需优化。
我们感觉已经很接近正解了。外层容斥枚举无法优化,因而考虑优化内层的 DP 。
我们想要知道的只是数量关系,并不想要实际数量。而数量关系可以通过运算与运算结果来表达。
典型的例子:(j>kLeftrightarrow j-k>0)
利用这样的性质,我们可以优化状态:
(g(i,j)):前 (i) 种烹饪方法,钦定食材比其他食材多用 (j) 次的方案数。
转移也很简单:
需要注意 (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( '
' );
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;
}