厨师准备给小朋友们制作 m m m 道菜,每道菜均使用 k k k 克原材料。为此,厨师购入了 n n n 种原材料,原材料从 1 1 1 到 n n n 编号,第 i i i 种原材料的质量为 d i d_i di 克。 n n n 种原材料的质量之和恰好为 m × k m \times k m×k 克,其中 d i d_i di 与 k k k 都是正整数。
制作菜品时,一种原材料可以被用于多道菜,但为了让菜品的味道更纯粹,厨师打算每道菜至多使用 2 2 2 种原材料。现在请你判断是否存在一种满足要求的制作方案。更具体地,方案应满足下列要求:
1 ≤ n ≤ 500 1 \leq n \leq 500 1≤n≤500, n − 2 ≤ m ≤ 5000 n - 2 \leq m \leq 5000 n−2≤m≤5000, m ≥ 1 m \geq 1 m≥1, 1 ≤ k ≤ 5000 1 \leq k \leq 5000 1≤k≤5000, ∑ i = 1 n d i = m × k \sum_{i=1}^{n}d_i = m \times k ∑i=1ndi=m×k
好题!
首先观察数据范围,发现 m ≥ n − 2 m\ge n-2 m≥n−2,就知道这题应该是什么分类讨论题了
再观察细致的数据范围,发现有 m = n − 1 m=n-1 m=n−1和 m ≥ n − 1 m\ge n-1 m≥n−1的部分分,所以从 m = n − 1 m=n-1 m=n−1下手,先把 d d d数组排序,有结论 d 1 + d n ≥ k d_1+d_n\ge k d1+dn≥k,然后我们用 1 1 1和 n n n做一道菜,把 1 1 1用完,然后就变成了一个 m − 1 , n − 1 m-1,n-1 m−1,n−1的规模更小的数据范围,当 m = 2 , n = 1 m=2,n=1 m=2,n=1时显然有解,所以 m = n − 1 m=n-1 m=n−1时有解, m > n − 1 m>n-1 m>n−1时,显然有 d n ≥ k d_n\ge k dn≥k,然后不停地用 d n d_n dn做菜,显然可以归约到 m = n − 1 m=n-1 m=n−1的情况
然后就只剩下 m = n − 2 m=n-2 m=n−2的情况了,手玩一下小数据进行观察, n = 3 n=3 n=3时无解, n = 4 n=4 n=4的充要条件为两个 d d d的和等于 k k k,这启发我们把 m = n − 2 m=n-2 m=n−2的情况划分成两个 m = n − 1 m=n-1 m=n−1的情况
结论: { d n } \{d_n\} {dn}有解当且仅当,可以划分成一个子集 S ⊂ { 1 , 2 , ⋯ , n } S\subset\{1,2,\cdots,n\} S⊂{1,2,⋯,n},使得 ∑ i ∈ S d i = ( ∣ S ∣ − 1 ) k \sum_{i\in S}d_i=(|S|-1)k ∑i∈Sdi=(∣S∣−1)k
充分性:这个子集和它的补集可以形成两个 m = n − 1 m=n-1 m=n−1的问题,所以肯定有解
必要性:如果两个原料做了一道菜,我们就给这两个原料连上一条边,由于 m = n − 2 m=n-2 m=n−2,所以这个图肯定不连通,解必定有办法划分成两个连通的图
这个可以用 01 01 01背包来判断,用 b i t s e t bitset bitset优化,复杂度就变成了 O ( n 2 k w ) O(\frac{n^2k}{w}) O(wn2k)
#include
#include
#include
#include
#include
using namespace std;
void read(int &res)
{
res=0;char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while('0'<=ch&&ch<='9') res=(res<<1)+(res<<3)+(ch^48),ch=getchar();
}
const int N=5e2+10,K=5e3+10;
int n,m,k,d[N+10];
struct node
{
int id,d;
}p[N+10];
void write(int res)
{
if(res>=10) write(res/10),putchar('0'+res%10);
else putchar('0'+res);
}
namespace sub1
{
bool cmp(node a,node b){return a.d<b.d;}
void solve(int n)
{
for(int i=1;i<=n;i++)
while(m>n-1&&p[i].d>=k)
{
write(p[i].id),putchar(' '),write(k),putchar('\n');
p[i].d-=k;
m--;
}
for(int i=n;i>=2;i--)
{
sort(p+1,p+1+i,cmp);
if(p[1].d==0) write(p[i].id),putchar(' '),write(k-p[1].d),putchar('\n');
else write(p[1].id),putchar(' '),write(p[1].d),putchar(' '),write(p[i].id),putchar(' '),write(k-p[1].d),putchar('\n');
p[i].d-=k-p[1].d,p[1]=p[i];
}
}
}
namespace sub2
{
bitset<N*K*2+10> f[N+10];
bool vis[N+10];
const int C=N*K;
void dfs(int h,int s)
{
if(h==0) return;
if(s+C-(d[h]-k)>=0&&s+C-(d[h]-k)<=C*2&&f[h-1][s+C-(d[h]-k)]) vis[h]=true,dfs(h-1,s-(d[h]-k));
else dfs(h-1,s);
}
void solve()
{
f[0].reset(),f[0].set(k+C);
for(int i=1;i<=n;i++)
{
if(d[i]-k>=0) f[i]=f[i-1]|(f[i-1]<<d[i]-k);
else f[i]=f[i-1]|(f[i-1]>>k-d[i]);
}
if(!f[n][C]) printf("-1\n");
else
{
for(int i=1;i<=n;i++) vis[i]=false;
dfs(n,0);
int top=0;
for(int i=1;i<=n;i++)
if(vis[i])
p[++top]=(node){i,d[i]};
m=top-1;
sub1::solve(top);
top=0;
for(int i=1;i<=n;i++)
if(!vis[i])
p[++top]=(node){i,d[i]};
m=top-1;
sub1::solve(top);
}
}
}
int main()
{
freopen("dish.in","r",stdin);
freopen("dish.out","w",stdout);
int T;
read(T);
for(;T--;)
{
read(n),read(m),read(k);
for(int i=1;i<=n;i++) read(d[i]);
if(m>=n-1)
{
for(int i=1;i<=n;i++) p[i].d=d[i],p[i].id=i;
sub1::solve(n);
}
else sub2::solve();
}
return 0;
}