[NOI2020] 制作菜品

CIL.[NOI2020] 制作菜品

本题有三个难点:留意到题面中的 n2m;证明;想到 bitset 优化。

首先,在很隐蔽的角落,有一句话 n2m5000。假如没看到这句话,就乖乖爆零罢。

结论1. mn1 时一定有解。

要证明这个结论,我们要分成三部分。

结论1.1. m=n1 时一定有解。

不妨将 a 数组递增排序。则,a1 必定 <k,因为所有数的平均值为 mkn=(n1)kn<k,则必有至少一个数满足 <k 的条件。

同时,我们也能发现,a1+an>k。因为,若 a1+ank,则 mk=i=1naid1+i=2nana1+(n1)(ka1)=(n1)k(n2)a1=mk(n2)a1。显然,当 n>2 时,因为 a1>0,所以 (n2)a1>0,故可以一次填完所有 a1,然后不足的部分就用 an 补,这样便同时少了一种原料和一道菜,转移到了 n1 的情形;而当 n=2 时,因为只要做一道菜,所以直接把剩余的两种原料怼一块即可。

结论1.2. m=n 时一定有解。

不妨仍将 a 数组递增排序。则,an 必定 k,因为所有数的平均值为 k,则必有至少一个数满足 k 的条件。

an=k,则可以用 an 单独做一道菜,转移到 n,m 均减少 1 的情形,这样不断递归下去,直到 n,m 均为 0(此时已经构造出一组解)或是 an>k。当 an>k 时,仍可以用 an 单独做一道菜,转移到 m=n1 的情形。

结论1.3. m>n 时一定有解。

递增排序后,an 必定 >k,因为所有数的平均值 >k,则必有至少一个数 >k。于是可以用 an 单独做一道菜,转移到 m 减一的情形。不断执行,直到到达 1.2 的场景。

结论2. m=n2 时,当且仅当其能被分成两个非空集合 U,V 使得 uUau=(|U|1)k 时,有解。

首先,其是充分的,因为对于 U,V 我们可以分别应用 1.1 中的结论直接构造出一组解来。

其次,其是必要的,因为 n2 条边连不出一张连通图,必定可以将所有原料分作两个集合使得不存在任何同时包含来自两个集合的原料的菜。

这样,我们就可以背包出一组解来了。具体而言,有经典套路是在上式中把 k 移到左侧,得到 uU(auk)=k。暴力背包复杂度是 O(n3k) 的。但是,因为01背包的数组是 bool 数组,所以可以使用 bitset 优化至 n3kω

(可能因为 bitset 开太大的缘故,本代码在 Windows 下会 RE,可以使用 CF 的 Custom Test 编译)

代码:

#include<bits/stdc++.h>
using namespace std;
int T,n,m,p,a[510],ord[510];
vector<vector<int> >v;
bool cmp(int u,int v){return a[u]<a[v];}
bitset<5000010>bs[502];
const int half=2500002;
bool sd[510];
void SOLVE(int M,int N,int *dro){
	while(M){
		sort(dro+1,dro+N+1,cmp);
		v.push_back({dro[1],a[dro[1]],dro[N],p-a[dro[1]]});
		a[dro[N]]-=p-a[dro[1]];
		dro[1]=dro[N--],M--;
	}
}
int main(){
	scanf("%d",&T);
	while(T--){
		scanf("%d%d%d",&n,&m,&p),v.clear();
		for(int i=1;i<=n;i++)scanf("%d",&a[i]),ord[i]=i;
		while(m>n){
			sort(ord+1,ord+n+1,cmp);
			v.push_back({ord[n],p}),a[ord[n]]-=p;
			m--;
		}
		while(n&&m==n){
			sort(ord+1,ord+n+1,cmp);
			v.push_back({ord[n],p}),a[ord[n]]-=p;
			m--;
			if(!a[ord[n]])n--;
		}
		if(m==n-1)SOLVE(m,n,ord);else if(n){
			bs[0].reset(),bs[0].set(half);
			for(int i=1;i<=n;i++){
//				printf("%d\n",i);
				if(a[i]==p)bs[i]=bs[i-1];
				if(a[i]>p)bs[i]=bs[i-1]|(bs[i-1]<<(a[i]-p));
				if(a[i]<p)bs[i]=bs[i-1]|(bs[i-1]>>(p-a[i]));
			}
			if(!bs[n].test(half-p)){puts("-1");continue;}
			for(int i=n,now=half-p;i;i--){
				if(bs[i-1].test(now-a[i]+p))sd[i]=true,now=now-a[i]+p;
				else sd[i]=false;
			}
//			for(int i=1;i<=n;i++)printf("%d ",sd[i]);puts("");
			sort(ord+1,ord+n+1,[](int x,int y){return sd[x]<sd[y];});
			for(int i=1;i<n;i++)if(!sd[ord[i]]&&sd[ord[i+1]])SOLVE(i-1,i,ord),SOLVE(n-i-1,n-i,ord+i);
		}
		for(auto i:v){for(auto j:i)printf("%d ",j);puts("");}
	}
	return 0;
} 

posted @   Troverld  阅读(73)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示