zoukankan      html  css  js  c++  java
  • [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} imes f(i-1,j-1,k)+a_{i,2} imes f(i-1,j,k-1) ]

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

    [sum_{k=1}^nsum_{j=1}^{lfloorfrac k 2 floor} f(n,j,k-j)[k-jle lfloorfrac k 2 floor] ]

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

    84pts

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

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

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

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

    [ ext{每种主要食材出现次数}lelfloorfrac{k}{2} floor ]

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

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

    上述性质顺便也说明了,钦定不合法食材的数量一定大于其他食材的总数,这就便于设计状态进行 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} imes f(i-1,j-1,k)+(s_i-a_{i,u}) imes f(i-1,j,k-1) ]

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

    100pts

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

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

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

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

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

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

    转移也很简单:

    [g(i,j)=g(i-1,j)+a_{i,u} imes g(i-1,j-1)+(s_i-a_{i,u}) imes 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( '
    ' );
    	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;
    }
    
  • 相关阅读:
    邮件发送
    生成缩略图
    截取字符串,中文安两个字符处理
    获取客户机MAC地址 根据IP地址 获取机器的MAC地址 / 获取真实Ip地址
    文件操作
    MD5加密 和 自定义加密解密
    Html标签替换(过滤掉html特殊符号)
    .net core 自定义中间件
    querySelector和querySelectorAll
    CSS下背景属性background的使用方法
  • 原文地址:https://www.cnblogs.com/crashed/p/13372918.html
Copyright © 2011-2022 走看看