LOJ3342 「NOI2020」制作菜品
有\(n\)种材料,每种有\(w_i\)单位。要分配给\(m\)个菜,要求:
每个菜至多两种材料组成,并且都是整数单位,且总和为\(k\)。
\(\sum w_i=mk\)
\(n\le 500,n-2\le m\le 5000\)
现在做思考程度都不如一年前,事实证明我真的比上一年菜了。
发现性质:如果把由两种材料组成的菜看成一条边,那么任意方案都可以调整成无环的方案。没卵用。
好啦以下才是题解:
先考虑部分分中的\(m=n-1\)的情况。首先对\(w\)从小到大排序。于是有:
- \(w_1<k\)。因为\(w_1\)不超过平均数\(\frac{n-1}{n}k\),所以小于\(k\)。
- \(w_1+w_n>k\)。反证:如果\(w_1+w_n\le k\),则\((n-1)k=\sum w_i\le k+(n-2)w_n\),于是\(k\le w_n<w_1+w_n\),矛盾。
可证只要满足\(m=n-1,\sum w_i=(n-1)k,w_i>0\),则一定有解:取\(w_1,w_n\)配对,\(w_1\)被消耗完,\(w_n\)不会被消耗完,然后进入同样满足条件的子问题。
当\(m>n-1\)时:如果\(\forall i,w_i\le k\),则此时一定是\(m=n,w_i=k\),显然有解;否则\(\exist i,w_i>k\),将这个\(w_i\)变成\(w_i-k\),\(m\)减一,还是个同样满足条件的子问题。
剩下的问题是\(m=n-2\)的情况:
充分必要条件:如果能把材料划分成两个集合,使得这两个集合都相当于一个独立的子问题(满足\(m=n-1\)的,\(\sum w_i=(n-1)k\))。(理解:如果把菜看成边,那么至少会连出两个连通块)
充分性显然,必要性:归纳,假设把最小的\(w_i\)消掉。无论怎样,消掉的\(n\)小于等于消掉的\(m\)(即\(\Delta(n-m)\le 0\),因为不能分成两个独立的子问题,所以不可能有\(n-1=m\),并且由于是用最小\(w_i\)和其它的消,不会出现自环,所以更不可能\(n>m\))于是变成了类似的子问题。
所以只需要搞个背包,找到集合\(S\)满足\(\sum_{i\in S}w_i-k=-k\)即可。用bitset优化DP。求方案不需要记前驱,因为容易推知从哪里转移过来是有解的。
using namespace std;
#include <bits/stdc++.h>
#define N 505
#define M 5005
#define O (N*M)
#define fi first
#define se second
#define mp make_pair
int n,m,k;
int w[N];
struct Ans{
int u,v,c;
} a[M];
int cnt;
multiset<pair<int,int> > s;
int ls[N],nl;
void work0(){
s.clear();
for (int i=0;i<nl;++i)
s.insert(mp(w[ls[i]],ls[i]));
/*
printf("ls : ");
for (int i=0;i<nl;++i)
printf("%d ",ls[i]);
printf("\n");
*/
while (!s.empty()){
multiset<pair<int,int> >::iterator p=s.begin(),q;
int x=p->fi,u=p->se;
s.erase(p);
if (s.size()==0 || x>=k){
a[++cnt]={u,0,k};
//printf("%d %d %d\n",u,0,k);
x-=k;
if (x>0)
s.insert(mp(x,u));
}
else{
q=--s.end();
int y=q->fi,v=q->se;
s.erase(q);
a[++cnt]={u,v,x};
//printf("%d %d %d\n",u,v,x);
y-=k-x;
if (y>0)
s.insert(mp(y,v));
}
}
}
void work1(){
static bitset<N*M*2> f[N];
f[0][0+O]=1;
for (int i=1;i<=n;++i)
if (w[i]-k>=0)
f[i]=f[i-1]|f[i-1]<<w[i]-k;
else
f[i]=f[i-1]|f[i-1]>>-(w[i]-k);
if (f[n][-k+O]==0){
printf("-1\n");
return;
}
nl=0;
for (int i=n,x=-k;i>=1;--i)
if (!f[i-1][x+O]){
x-=w[i]-k;
ls[nl++]=i;
}
work0();
static int T[N],nt;
nt=0;
for (int i=n,j=0;i>=1;--i){
for (;j<nl && ls[j]>i;++j);
if (j==nl || ls[j]!=i)
T[nt++]=i;
}
memcpy(ls,T,sizeof(int)*nt);
nl=nt;
work0();
}
int main(){
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
freopen("dish.in","r",stdin);
freopen("dish.out","w",stdout);
int T;
scanf("%d",&T);
while (T--){
scanf("%d%d%d",&n,&m,&k);
for (int i=1;i<=n;++i)
scanf("%d",&w[i]);
cnt=0;
if (m==n-2)
work1();
else{
nl=0;
for (int i=1;i<=n;++i)
ls[nl++]=i;
work0();
}
for (int i=1;i<=cnt;++i)
if (a[i].v)
printf("%d %d %d %d\n",a[i].u,a[i].c,a[i].v,k-a[i].c);
else
printf("%d %d\n",a[i].u,k);
//printf("\n");
}
return 0;
}