题目
题目链接:https://www.luogu.com.cn/problem/P6775
厨师准备给小朋友们制作 \(m\) 道菜,每道菜均使用 \(k\) 克原材料。为此,厨师购入了 \(n\) 种原材料,原材料从 \(1\) 到 \(n\) 编号,第 \(i\) 种原材料的质量为 \(d_i\) 克。\(n\) 种原材料的质量之和恰好为 \(m \times k\) 克,其中 \(d_i\) 与 \(k\) 都是正整数。
制作菜品时,一种原材料可以被用于多道菜,但为了让菜品的味道更纯粹,厨师打算每道菜至多使用 \(2\) 种原材料。现在请你判断是否存在一种满足要求的制作方案。更具体地,方案应满足下列要求:
- 共做出 \(m\) 道菜。
- 每道菜至多使用 \(2\) 种原材料。
- 每道菜恰好使用 \(k\) 克原材料。
- 每道菜使用的每种原材料的质量都为正整数克。
- \(n\) 种原材料都被恰好用完。
若存在满足要求的制作方案,你还应该给出一种具体的制作方案。
思路
当 \(m=n-1\) 时,我们每次取最少的全部,剩下的用最大的填满,这样就转换成了有 \(n-1\) 种原料,\(m-1\) 种菜品的问题,一直做下去即可得到答案。
容易证明最少的那堆不可能超过 \(k\),最大的那堆一定不小于 \(k\)。所以当 \(m=n-1\) 时一定有解。
由于数据范围小,这一部分乱搞都可以过。我直接采用了 \(O(nm\log n)\) 的无脑算法。
当 \(m\geq n\) 时,我们每次取最大的单独做一份菜品。直到 \(m=n-1\) 为止。这样又转换成了上一种情况。
容易证明每次最大那一堆数量不小于 \(k\)。时间复杂度 \(O(m+nm\log n)\)。
当 \(m=n-2\) 时,能有解当且仅当所有原料能分成两部分,这两部分都满足 \(m=n-1\) 且 \(\sum d_i=km\)。
那么我们只需要判断能否从这些原料中选择若干个使得选出的这些原料的重量总和等于 \((\) 选择原料数量 \(-1)\times k\)。那么可以转换为 \(\sum(d_i-k)=-k\)。这是一个 01 背包的模板。设 \(f[i][j]\) 表示前 \(i\) 个原料能否选出若干个使得和为 \(j\),如果 \(f[i][-k]=1\) 则有解。倒序求路径即可。
时间复杂度 \(O(n^2m)\),并不是一个可以接受的范围。用 \(\operatorname{bitset}\) 优化即可优化至 \(O(\frac{n^2m}{32})\)。
总时间复杂度 \(O(T·(\frac{n^2m}{32}+nm\log n))\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=510,M=5010;
int Q,n,m,t;
bool vis[N];
bitset<N*M*2> f[N];
int read()
{
int d=0; char ch=getchar();
while (!isdigit(ch)) ch=getchar();
while (isdigit(ch)) d=(d<<3)+(d<<1)+ch-48,ch=getchar();
return d;
}
struct node
{
int x,id;
}a[M],b[M];
bool cmp(node x,node y)
{
return x.x>y.x;
}
void solve2(node a[N],int n,int m)
{
while (m--)
{
sort(a+1,a+1+n,cmp);
while (!a[n].x) n--;
if (a[n].x<t)
{
printf("%d %d %d %d\n",a[n].id,a[n].x,a[1].id,t-a[n].x);
a[1].x-=(t-a[n].x);
}
else printf("%d %d\n",a[n].id,a[n].x);
a[n].x=0;
}
}
void solve1()
{
sort(a+1,a+1+n,cmp);
for (int i=1;m>=n;m--)
{
a[i].x-=t;
printf("%d %d\n",a[i].id,t);
if (a[i].x<t) i++;
}
solve2(a,n,m);
}
void solve3()
{
memset(vis,0,sizeof(vis));
for (int i=0;i<=n;i++)
f[i].reset();
f[0][N*M]=1;
for (int i=1;i<=n;i++)
{
f[i]=f[i-1];
if (a[i].x-t>=0) f[i]|=f[i-1]<<(a[i].x-t);
else f[i]|=f[i-1]>>(t-a[i].x);
}
if (!f[n][N*M-t])
{
printf("-1\n");
return;
}
for (int i=n,j=N*M-t;i>=1;i--)
if (!f[i-1][j]) vis[i]=1,j-=a[i].x-t;
int cnt=0;
for (int i=1;i<=n;i++)
if (vis[i]) b[++cnt]=a[i];
solve2(b,cnt,cnt-1);
cnt=0;
for (int i=1;i<=n;i++)
if (!vis[i]) b[++cnt]=a[i];
solve2(b,cnt,cnt-1);
}
int main()
{
Q=read();
while (Q--)
{
n=read(); m=read(); t=read();
for (int i=1;i<=n;i++)
{
a[i].x=read();
a[i].id=i;
}
if (m==n-2) solve3();
else if (m==n-1) solve2(a,n,m);
else if (m>=n) solve1();
}
return 0;
}