厨师准备给小朋友们制作
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;
}