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;
}
posted @ 2022-07-17 21:00  YouthRhythm  阅读(39)  评论(0)    收藏  举报