• 「NOI2020」制作菜品


    题目

    点这里看题目。

    分析

    一读题,发现不太会,那就看看数据范围吧。

    一读数据范围,马上注意到 \(m=n-1,m\ge n-1\) 这样的部分分,而且排布位置靠前、分值占比不算大——说明两点:

    1. 这个部分分本身难度不大,但是应当很有启发性。出题人在暗示切入口。

    2. \(m\ge n-1\) 一旦解决,之后只需要解决 \(m=n-2\),问题的复杂性大大降低。

    从解决问题的角度看,\(m=n-1\) 很有可能和“树”有关,而本题有明确的图论背景:我们可以将 \(n\) 种原材料看作是图的顶点,而 \(m\) 道菜看作是连接两种原材料的边。当 \(m=n-1\) 的时候,最终的方案很有可能对应的是“树”——实际上,可以发现此时图由一棵树和若干独立的自环组成

    独立的自环是由 \(d_i=k\) 的原材料形成的。树如何构造?贪心地想,\(d\) 越小的原材料越不容易做进一道菜里面,所以我们尽早解决掉它们,从最小值开始。为了保证能够解决掉最小值,我们最好动用尽量大的值,也就是最大值。每次拿 \(d\) 最小和 \(d\) 最大的原材料来做成一道菜,通过数值分析可以发现这的确是可行的。

    Proof.

    我们可以归纳地证明,\(m=n-1\) 且所有的 \(d\) 均为正整数时,必然可以构造出方案。

    归纳边界是 \(n=2\),此时两种原材料直接做一道菜就好。

    \(n>2\) 的时候,我们将 \(d_{\min}\)\(d_\max\) 拿来做菜。已知 \(\sum d=mk=(n-1)k\),所以 \(d_\max\ge \frac{(n-1)k-d_{\min}}{n-1}=k-\frac{d_\min}{n-1}\)。那么就应该有 \(d_\min+d_\max\ge k\),且 \(d_\max-d_\min>0\)。因此,在这样做了一道菜之后,我们就归约到了 \(n,m\) 都小 \(1\) 的情况。

    Remark.

    我的评价是,这样的配对方式不在于想不想得到,而在于有没有勇气去验证它是对的

    考虑 \(m>n-1\) 的情况。此时可能出现 \(>2\) 的环,但是一旦存在一种包含 \(>2\) 的环的最优解,我们就可以通过调整将这个环变成一个自环。所以 \(m>n-1\) 实际上只需要考虑自环便可归约到 \(m=n-1\) 的情况。

    拿哪些原材料来造自环?由于上述结论对于 \(d\) 的任意性,我们可以每次选择一个 \(>m\) 的原材料来造自环。可以发现这样的过程一定可以进行到 \(m=n-1\) 为止。

    最后考虑 \(m=n-2\) 的情况。从图上来看,\(m=n-2\) 意味着图不连通,也就意味着我们可以将点集划分成两个子集,子集内部则自然归约到了 \(m=n-1\) 的情况。那么,我们就要求找到 \(V'\subseteq V\),使得 \(\sum_{x\in V'}d_x=(|V'|-1)k\)。将每个 \(d\) 都减去 \(k\) 后做背包,找到和为 \(-k\) 的一个子集也就找到了 \(V'\)

    由于 \(\sum d=mk\),因此当 \(m=n-2\) 的时候,背包容量只需要开到 \(O(nk)\),用 bitset 优化即可做到 \(O(\frac{n^2k}{\omega})\)

    Note.

    思考的时候细致一点。不要误以为做背包的时候 \(m\) 很大,然后怀疑这个做法的正确性......

    代码

    #include <bitset>
    #include <cstdio>
    #include <utility>
    #include <algorithm>
    #include <functional>
    
    #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 INF = 1e9;
    const int MAXN = 505, MAXS = 5000 * 500 + 5;
    
    template<typename _T>
    inline void Read( _T &x ) {
    	x = 0; char s = getchar(); bool f = false;
    	while( ! ( '0' <= s && s <= '9' ) ) { 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>
    inline void Write( _T x ) {
    	if( x < 0 ) putchar( '-' ), x = -x;
    	if( 9 < x ) Write( x / 10 );
    	putchar( x % 10 + '0' );
    }
    
    typedef std :: pair<int, int> Inte;
    
    Inte org[MAXN];
    
    int N, M, K;
    
    namespace Tree {
    	Inte D[MAXN];
    	bool used[MAXN];
    	
    	int m, n;
    
    	void Solve() {
    		rep( i, 1, n ) used[i] = false;
    		rep( i, 1, m ) {
    			int mn = -1, mx = -1;
    			rep( j, 1, n ) {
    				if( used[j] ) continue;
    				if( mn == -1 || D[j].first < D[mn].first ) mn = j;
    			}
    			rep( j, 1, n ) {
    				if( used[j] || j == mn ) continue;
    				if( mx == -1 || D[j].first > D[mx].first ) mx = j;
    			}
    			printf( "%d %d %d %d\n", D[mn].second, D[mn].first,
    									 D[mx].second, K - D[mn].first );
    			D[mx].first -= K - D[mn].first, used[mn] = true;
    		}
    	}
    }
    
    namespace Special {
    	std :: bitset<MAXS << 1> dp[MAXN];
    
    	int side[MAXN];
    
    	void FindOut( const int &i, const int &j ) {
    		if( i == 0 ) return ;
    		if( dp[i - 1].test( j ) )
    			side[i] = 0, FindOut( i - 1, j );
    		else {
    			side[i] = 1;
    			if( org[i].first < K )
    				FindOut( i - 1, j + K - org[i].first );
    			else
    				FindOut( i - 1, j - org[i].first + K );
    		}
    	}
    
    	void Solve() {
    		rep( i, 0, N ) dp[i].reset();
    		dp[0].set( N * K );
    		rep( i, 1, N ) {
    			dp[i] = dp[i - 1];
    			if( org[i].first < K )
    				dp[i] |= dp[i - 1] >> ( K - org[i].first );
    			else
    				dp[i] |= dp[i - 1] << ( org[i].first - K );
    		}
    		if( ! dp[N].test( ( N - 1 ) * K ) ) {
    			puts( "-1" );
    			return ;
    		}
    		FindOut( N, ( N - 1 ) * K );
    		Tree :: n = 0;
    		rep( i, 1, N ) if( side[i] )
    			Tree :: D[++ Tree :: n] = org[i];
    		Tree :: m = Tree :: n - 1;
    		Tree :: Solve();
    		Tree :: n = 0;
    		rep( i, 1, N ) if( ! side[i] )
    			Tree :: D[++ Tree :: n] = org[i];
    		Tree :: m = Tree :: n - 1;
    		Tree :: Solve();
    	}
    }
    
    int main() {
    	freopen( "dish.in", "r", stdin );
    	freopen( "dish.out", "w", stdout );
    	int T; Read( T );
    	while( T -- ) {
    		Read( N ), Read( M ), Read( K );
    		rep( i, 1, N ) Read( org[i].first ), org[i].second = i;
    		for( int i = 1 ; i <= N && M > N - 1 ; i ++ )
    			while( org[i].first > K && M > N - 1 ) {
    				printf( "%d %d\n", org[i].second, K );
    				org[i].first -= K, M --;
    			}
    		rep( i, 1, N ) 
    			if( org[i].first == K ) {
    				printf( "%d %d\n", org[i].second, K );
    				M --, org[i].first -= K;
    			}
    		std :: sort( org + 1, org + 1 + N, std :: greater<Inte> () );
    		while( N > 0 && ! org[N].first ) N --;
    		if( M >= N - 1 ) {
    			Tree :: m = M;
    			Tree :: n = N;
    			rep( i, 1, N ) 
    				Tree :: D[i] = org[i];
    			Tree :: Solve();
    		} else 
    			Special :: Solve();
    	}
    	return 0;
    }
    
  • 相关阅读:
    mycat 查询sql 报错
    mysql 主从 binlog
    数据库分库分表思路
    JavaScript数组知识
    JS判断当前页面是在 QQ客户端/微信客户端/iOS浏览器/Android浏览器/PC客户端
    js汉字转换为拼音
    工作中常用到的JS验证
    自动部署服务器代码
    php Excel 导入
    PHP 模拟http 请求
  • 原文地址:https://www.cnblogs.com/crashed/p/16547239.html
Copyright © 2020-2023  润新知