题目
点这里看题目。
分析
一读题,发现不太会,那就看看数据范围吧。
一读数据范围,马上注意到 \(m=n-1,m\ge n-1\) 这样的部分分,而且排布位置靠前、分值占比不算大——说明两点:
-
这个部分分本身难度不大,但是应当很有启发性。出题人在暗示切入口。
-
\(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;
}