LOJ3342 「NOI2020」制作菜品

\(n\)种材料,每种有\(w_i\)单位。要分配给\(m\)个菜,要求:

每个菜至多两种材料组成,并且都是整数单位,且总和为\(k\)

\(\sum w_i=mk\)

\(n\le 500,n-2\le m\le 5000\)


现在做思考程度都不如一年前,事实证明我真的比上一年菜了。

发现性质:如果把由两种材料组成的菜看成一条边,那么任意方案都可以调整成无环的方案。没卵用。

好啦以下才是题解:

先考虑部分分中的\(m=n-1\)的情况。首先对\(w\)从小到大排序。于是有:

  1. \(w_1<k\)。因为\(w_1\)不超过平均数\(\frac{n-1}{n}k\),所以小于\(k\)
  2. \(w_1+w_n>k\)。反证:如果\(w_1+w_n\le k\),则\((n-1)k=\sum w_i\le k+(n-2)w_n\),于是\(k\le w_n<w_1+w_n\),矛盾。

可证只要满足\(m=n-1,\sum w_i=(n-1)k,w_i>0\),则一定有解:取\(w_1,w_n\)配对,\(w_1\)被消耗完,\(w_n\)不会被消耗完,然后进入同样满足条件的子问题。

\(m>n-1\)时:如果\(\forall i,w_i\le k\),则此时一定是\(m=n,w_i=k\),显然有解;否则\(\exist i,w_i>k\),将这个\(w_i\)变成\(w_i-k\)\(m\)减一,还是个同样满足条件的子问题。

剩下的问题是\(m=n-2\)的情况:

充分必要条件:如果能把材料划分成两个集合,使得这两个集合都相当于一个独立的子问题(满足\(m=n-1\)的,\(\sum w_i=(n-1)k\))。(理解:如果把菜看成边,那么至少会连出两个连通块)

充分性显然,必要性:归纳,假设把最小的\(w_i\)消掉。无论怎样,消掉的\(n\)小于等于消掉的\(m\)(即\(\Delta(n-m)\le 0\),因为不能分成两个独立的子问题,所以不可能有\(n-1=m\),并且由于是用最小\(w_i\)和其它的消,不会出现自环,所以更不可能\(n>m\))于是变成了类似的子问题。

所以只需要搞个背包,找到集合\(S\)满足\(\sum_{i\in S}w_i-k=-k\)即可。用bitset优化DP。求方案不需要记前驱,因为容易推知从哪里转移过来是有解的。


using namespace std;
#include <bits/stdc++.h>
#define N 505
#define M 5005
#define O (N*M)
#define fi first
#define se second
#define mp make_pair
int n,m,k;
int w[N];
struct Ans{
	int u,v,c;
} a[M];
int cnt;
multiset<pair<int,int> > s;
int ls[N],nl;
void work0(){
	s.clear();
	for (int i=0;i<nl;++i)
		s.insert(mp(w[ls[i]],ls[i]));
	/*
	printf("ls : ");
	for (int i=0;i<nl;++i)
		printf("%d ",ls[i]);
	printf("\n");
	*/
	while (!s.empty()){
		multiset<pair<int,int> >::iterator p=s.begin(),q;
		int x=p->fi,u=p->se;
		s.erase(p);
		if (s.size()==0 || x>=k){
			a[++cnt]={u,0,k};
			//printf("%d %d %d\n",u,0,k);
			x-=k;
			if (x>0)
				s.insert(mp(x,u));
		}
		else{
			q=--s.end();
			int y=q->fi,v=q->se;
			s.erase(q);
			a[++cnt]={u,v,x};
			//printf("%d %d %d\n",u,v,x);
			y-=k-x;
			if (y>0)
				s.insert(mp(y,v));
		}
	}
}
void work1(){
	static bitset<N*M*2> f[N];
	f[0][0+O]=1;
	for (int i=1;i<=n;++i)
		if (w[i]-k>=0)
			f[i]=f[i-1]|f[i-1]<<w[i]-k;
		else
			f[i]=f[i-1]|f[i-1]>>-(w[i]-k);
	if (f[n][-k+O]==0){
		printf("-1\n");
		return;
	}
	nl=0;
	for (int i=n,x=-k;i>=1;--i)
		if (!f[i-1][x+O]){
			x-=w[i]-k;
			ls[nl++]=i;
		}
	work0();
	static int T[N],nt;
	nt=0;
	for (int i=n,j=0;i>=1;--i){
		for (;j<nl && ls[j]>i;++j);
		if (j==nl || ls[j]!=i)
			T[nt++]=i;
	}
	memcpy(ls,T,sizeof(int)*nt);
	nl=nt;
	work0();
}		
int main(){
	//freopen("in.txt","r",stdin);
	//freopen("out.txt","w",stdout);
	freopen("dish.in","r",stdin);
	freopen("dish.out","w",stdout);
	int T;
	scanf("%d",&T);
	while (T--){
		scanf("%d%d%d",&n,&m,&k);
		for (int i=1;i<=n;++i)
			scanf("%d",&w[i]);
		cnt=0;
		if (m==n-2)
			work1();
		else{
			nl=0;
			for (int i=1;i<=n;++i)
				ls[nl++]=i;
			work0();
		}
		for (int i=1;i<=cnt;++i)
			if (a[i].v)
				printf("%d %d %d %d\n",a[i].u,a[i].c,a[i].v,k-a[i].c);
			else
				printf("%d %d\n",a[i].u,k);
		//printf("\n");
	}
	return 0;
}
posted @ 2021-05-06 08:19  jz_597  阅读(58)  评论(0编辑  收藏  举报