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