[NOI2020] 制作菜品

\(\text{Problem}:\)[NOI2020] 制作菜品

\(\text{Solution}:\)

关键性质 \(1\):当 \(m\geq n-1\) 时,一定有解。

关键性质 \(2\):当 \(m=n-2\) 时若有解,当且仅当能分出两组 \(m=n-1\) 的不相交集合。

构造一种制作方案,使得 \(m\geq n-1\) 时一定有解。

\(1\leq d_{1}\leq d_{2}\leq\cdot\cdot\cdot\leq d_{n}\),下面进行分类讨论:

  • \(d_{1}\geq k\),此时满足 \(m\geq n\),用 \(d_{1}\) 对应的食材单独做一道菜。易知操作结束后 \(m\) 仍大于等于 \(n-1\)
  • \(d_{1}<k\),此时满足 \(d_{1}+d_{n}\geq k\),用 \(d_{1}\)\(d_{n}\) 对应的食材做一道菜。易知操作结束后 \(m\) 仍大于等于 \(n-1\)

以上构造方式可以在 \(O(n^{2})\)\(O(n\log n)\) 的时间复杂度内模拟得解。

现在证明关键性质 \(2\) 的正确性:若 \(d_{1}+d_{n}<k\),此时无法配对 \(d_{1}\) 对应的食材,必然无解。否则,若不存在 \(d_{x}+d_{y}=k\) 的形式,那么每组成一道菜至多减少一种食材,而 \(n=3,m=1\) 时显然无解。故必然存在某一时刻,满足 \(d_{x}+d_{y}=k\),这样就可以分出两组 \(m=n-1\) 的不相交集合。

\(d_{i}=d_{i}-k\),问题转化为是否存在一个子集的权值和为 \(-k\)。这显然是一个背包问题,直接暴力做可以在 \(O(n^{2}k)\) 的时间复杂度内解决。

进一步的,对于此类可行性背包,可以利用 \(\text{bitset}\) 优化转移。总时间复杂度 \(O(\frac{n^{2}k}{\omega})\),可以通过。

\(\text{Code}:\)

#include <bits/stdc++.h>
#pragma GCC optimize(3)
//#define int long long
#define ri register
#define mk make_pair
#define fi first
#define se second
#define pb push_back
#define eb emplace_back
#define is insert
#define es erase
#define vi vector<int>
#define vpi vector<pair<int,int>>
using namespace std; const int N=504;
inline int read()
{
	int s=0, w=1; ri char ch=getchar();
	while(ch<'0'||ch>'9') { if(ch=='-') w=-1; ch=getchar(); }
	while(ch>='0'&&ch<='9') s=(s<<3)+(s<<1)+(ch^48), ch=getchar();
	return s*w;
}
int n,m,K,a[N];
struct Node { int w,id; }d[N]; int cnt;
inline void Doit(int up)
{
	set<pair<int,int> > Q;
	for(ri int i=1;i<=cnt;i++) Q.is(mk(d[i].w,d[i].id));
	pair<int,int> none=mk(0,0);
	for(ri int i=1;i<=up;i++)
	{
		pair<int,int> x=(*Q.begin());
		pair<int,int> y=none;
		if((int)Q.size()>1) y=(*Q.rbegin());
		if(x.fi>=K)
		{
			printf("%d %d\n",x.se,K);
			Q.erase(Q.find(x));
			x.fi-=K;
			if(x.fi) Q.insert(x);
		}
		else
		{
			printf("%d %d %d %d\n",x.se,x.fi,y.se,K-x.fi);
			Q.erase(Q.find(x)), Q.erase(Q.find(y));
			y.fi-=K-x.fi;
			if(y.fi) Q.insert(y);
		}
	}
}
bitset<5000010> f[N]; int FIR=2500004;
int vis[N];
inline void Solve()
{
	f[0].reset(), f[0][FIR]=1;
	for(ri int i=1;i<=n;i++)
	{
		if(a[i]>=K) f[i]=f[i-1]|(f[i-1]<<(a[i]-K));
		else f[i]=f[i-1]|(f[i-1]>>(K-a[i]));
	}
	if(!f[n][FIR-K]) { puts("-1"); return; }
	cnt=0;
	for(ri int i=n,y=FIR-K;i;i--)
	{
		if(f[i-1][y-(a[i]-K)])
		{
			y-=a[i]-K;
			d[++cnt]=(Node){a[i],i};
			vis[i]=1;
		}
	}
	Doit(cnt-1);
	cnt=0;
	for(ri int i=1;i<=n;i++) if(!vis[i]) d[++cnt]=(Node){a[i],i};
	Doit(cnt-1);
	memset(vis,0,sizeof(vis));
}
signed main()
{
	for(ri int T=read();T;T--)
	{
		n=read(), m=read(), K=read();
		for(ri int i=1;i<=n;i++) a[i]=read();
		if(m>=n-1)
		{
			cnt=0;
			for(ri int i=1;i<=n;i++) d[++cnt]=(Node){a[i],i};
			Doit(m); continue;
		}
		Solve();
	}
	return 0;
}
posted @ 2021-05-11 12:44  zkdxl  阅读(70)  评论(1编辑  收藏  举报