ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

LOJ3342 「NOI2020」制作菜品

2021-05-06 08:32:59  阅读:223  来源: 互联网

标签:le LOJ3342 int sum ++ NOI2020 菜品 nl define


有\(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;
}

标签:le,LOJ3342,int,sum,++,NOI2020,菜品,nl,define
来源: https://www.cnblogs.com/jz-597/p/14733695.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有