LOJ #3342. 「NOI2020」制作菜品
题目叙述
\(n\) 种食材,每种食材有 \(d_i\) 千克,要用这些食材做 \(m\) 道菜,每道菜重 \(k\) 千克。必须保证每道菜最多使用两种食材,构造方案。
题解
首先考虑 \(m=n-1\) 的情况,我们证明 \(d_1\le d_2\le \cdots \le d_n\) ,那么 \(d_1+d_n\ge k\) 。
采用反证法,如果不是,那么一定有 \(d_1\le _d2\le \cdots\le d_n<k-d_1\),因此 \(\sum_{i=1}^nd_1+\cdots+d_n<d_1+(n-1)(k-d_1)=(n-1)k-(n-2)d_1<(n-1)k\) 。可能要把 \(n=1\) 特判掉之类的。这时候每次将首尾配对之后首尾都去掉 \(k\) 。
如果 \(m\ge n\) ,那么最大数一定比 \(k\) 大。这是因为最大数 \(\ge\) 平均数,平均数至少是 \(k\) 。
所以这时每次将最大数去掉 \(k\) 就可以了。
剩下的情况就是 \(m=n-2\) ,\(n-2\) 是什么的,其实是把原问题拆成了两个 \(n-1\) 的问题。所以就变成了 \(-2\) 。
为什么可以拆成两个呢?如果将两个用在一个菜里面的食材连一条边,那么一定有连通块数量至少为 2 。因此肯定可以划分为两个 \(n-1\) 的部分。
所以只需要找到部分 \(s\) 满足 \(\sum_{i\in s}d_i=(|s|-1)k\) 。这是容易的,\(\sum_{i\in s}(d_i-k)=-k\) ,直接做一遍背包就可以了。
有人问怎么还原方案。开始我以为必须滚动数组,那样的话还原方案好像会比较麻烦。但后来发现好像不用。。。动态规划转移无非就是保持不动和选择当前数,保持不动会产生干扰,因此考察第一个必须通过选择当前数而非保持不动得到的状态,选上就好了。
总结
- 对 \(n-2\) 的敏感认识,需要发现是两个 \(n-1\) 拼起来的。
- 对 Phoenix and Diamonds 那题类似的模型有敏感认识。就是说最小数+最大数一定可行。
代码
#include <cstdio>
#include <iostream>
#include <vector>
#include <cassert>
#include <bitset>
#define macro_expand(x) #x
#define print_macro(x) printf("%s\n",macro_expand(x))
#define FOR(i,l,r) for(int i=(l),i##ADJK=(r);i<=i##ADJK;++i)
#define ROF(i,r,l) for(int i=(r),i##ADJK=(l);i>=i##ADJK;--i)
using namespace std;
typedef long long LL;
const int MN=505,MM=5005;
template<typename T>bool chkmax(T &x,const T y){return (x<y)?(x=y,1):0;}
template<typename T>bool chkmin(T &x,const T y){return (x>y)?(x=y,1):0;}
int T,N,M,K,d[MN];
struct Node{
int i,w;
Node():i(0),w(0){}
Node(int _i,int _w):i(_i),w(_w){}
};
#define fi first
#define se second
vector<pair<Node,Node> > ans;
void construct(vector<int> id){
int sum=0;
for(int i:id)sum+=d[i];
int res=sum/K;
FOR(i,1,res){
int _min=1e9,_max=0,x=0,y=0;
for(int j:id){
if(d[j]==0)continue;
if(chkmin(_min,d[j]))x=j;
if(chkmax(_max,d[j]))y=j;
}
if(res>=(int)id.size()&&d[y]>=K){
--res;
d[y]-=K;
ans.push_back(make_pair(Node(y,K),Node(0,0)));
}else{
assert((d[x]+d[y])>=K);
ans.push_back(make_pair(Node(x,d[x]),Node(y,K-d[x])));
d[y]-=(K-d[x]);
d[x]=0;
}
}
}
int main(){
freopen("dish.in","r",stdin);
freopen("dish.out","w",stdout);
scanf("%d",&T);
while(T--){
scanf("%d%d%d",&N,&M,&K);
FOR(i,1,N)scanf("%d",&d[i]);
ans.clear();
if(M>=N-1){
vector<int> id;
FOR(i,1,N)id.push_back(i);
construct(id);
}else{
static int val[MN];
int add=0;
FOR(i,1,N){
val[i]=d[i]-K;
if(val[i]<0)add+=-val[i];
}
static bitset<2*MN*MM> f[MN];
FOR(i,0,N)f[i].reset();
f[0][add]=1;
FOR(i,1,N){
if(val[i]>0)f[i]=f[i-1]|(f[i-1]<<val[i]);
else f[i]|=f[i-1]|(f[i-1]>>(-val[i]));
}
if(!f[N][-K+add])printf("-1\n");
else{
int now=-K,p=N;
vector<int> id1;
static bool tag[MN];
FOR(i,1,N)tag[i]=0;
while(now){
while(p&&f[p][add+now])--p;
now-=val[p+1];
id1.push_back(p+1);
tag[p+1]=1;
}
vector<int> id2;
FOR(i,1,N)if(!tag[i])id2.push_back(i);
construct(id1),construct(id2);
}
}
for(auto x:ans){
if(!x.se.i)printf("%d %d\n",x.fi.i,x.fi.w);
else printf("%d %d %d %d\n",x.fi.i,x.fi.w,x.se.i,x.se.w);
}
}
fclose(stdin);
fclose(stdout);
return 0;
}

浙公网安备 33010602011771号