洛谷 P6775 - 制作菜品
第 100 篇博客祭!(UPD 2020.9.8:发现不是的,这是第 101 篇,之前写的 CF 809D 题解忘了分类了)
话说这题为啥是个黑题啊,感觉我能独立 AC 的题都不能算黑题(
然而在考场上还写挂了,有调味料选了 \(0\mathrm g\),而输出格式规定 \(y>0\)(悲)。这启示我们下次写 spj 题的时候,如果自己写 checker 判正确性,一定要把题面里所有的限制都判一下……
洛谷题目页面传送门
题意见洛谷。
首先,显然有 \(\sum d_i=mk\)。以下设 \(mn=\min\{d_i\},mx=\max\{d_i\}\),则 \(mx\geq \dfrac mnk,mn\leq \dfrac mnk\)。
注意到奇奇怪怪的数据范围,不妨先从 \(m=n\) 入手。此时显然有 \(mx\geq k,mn\leq k\)。考虑配出一道菜,那么可以优先将 \(mn\) 用掉,然后用 \(mx\) 解决剩下的 \(k-mn\),由于 \(mn\geq1\),所以一定有 \(mx>k-mn\),\(mx\) 用不完。可以保证恰好用掉一个原料。此时 \(n,m\) 都减一,转化为了一个规模小一级的 \(m=n\) 问题。边界是 \(n=m=0\)。
然后考虑 \(m>n\)。显然有 \(mx>k\)。直接用 \(mx\) 配出一个菜,还用不完,只有 \(m\) 减一。就这样一直减减减,直到 \(m=n\) 为止。
然后考虑 \(m=n-1\)。\(m=1,n=2\) 时直接配就可以了(这是边界)。否则,考虑配出一道菜。那么能否配出一道菜呢?这就要满足 \(mx+mx'\geq k\),其中 \(mx'\) 表示第二大的原料。肉眼只能得到 \(mx\geq \dfrac{n-1}nk\) 这样一个条件,仿佛不能保证一定能配出。但是经过不懈的尝试,还是证出来了一定能。
证明:反证法。假设不能,则 \(mx+mx'<k\),即 \(mx'<k-mx\)。又 \(\sum d_i\leq (n-1)mx'+mx=u\),显然当 \(mx=\dfrac{n-1}nk\) 时,\(u\) 的上限最松为 \(u<(n-1)\left(k-\dfrac{n-1}nk\right)+\dfrac{n-1}nk=\dfrac{2n-2}nk\)。所以 \(\sum d_i<\dfrac{2n-2}nk\)。又 \(n>2\),所以 \(\dfrac2n<1\),所以 \(\dfrac{2n-2}nk<(n-1)k=\sum d_i\),矛盾。得证。
所以只需要优先用 \(mx'\),再用 \(mx\)。但是就怕 \(mx'>k\),用不完,那么用掉之后就 \(m=n-2\) 了,哦吼,未知问题。那怎么办呢?注意到显然有 \(mn<k\),先把 \(mn\) 用掉即可。这样可能会转化到一个规模小一级的 \(m=n-1\),也有可能 \(m=n\)(可能会 \(mx+mx'=k\))。
至此,\(m\geq n-1\) 就归纳做完了。\(m=n-2\) 貌似没有啥思路?注意到可以将原料和菜分成两部分,使得每部分内部的原料和菜质量相等,并且 \(m_1=n_1-1,m_2=n_2-1\),这样就可以做了。然后猜想,若不能分,则无解,并试图证明。然后就证出来了(我考场上没证,直接写的)。
证明:考虑证明逆否命题:若有解,则能分。考虑将每个菜看作节点,将每个原料也看作节点,若一个菜用了一个原料,则它们之间有边,构成一张二分图。显然,有 \(n+m\) 个点,最多有 \(2m\) 条边。要想图连通,必要条件是 \(2m\geq n+m-1\),即 \(m\geq n-1\)。现在 \(m=n-2\) 则图一定不连通。然后显然一定存在连通分量的 \(m'=n'-1\),因为全是 \(m'\geq n'\) 的话最终就 \(m\geq n\) 了。所以这个连通分量以及它的补图是独立的,各自有解。得证。
接下来就是怎么分的事了。设分成两个集合 \(S,T\),则对 \(A=S\) 或 \(A=T\) 都有 \(\sum\limits_{i\in A}(d_i-k)=-k\)。这是一个很简单的 01 背包,最终还原一下路径即可。定义域是 \(\mathrm O(nk)\) 的,要更新 \(n\) 遍,复杂度 \(\mathrm O\!\left(n^2k\right)\),吃不消。注意到 DP 数组只记录能否凑出的布尔值,转移时 bitset
移位 + 按位或即可。
总时间复杂度 \(\mathrm O\!\left(\dfrac{Tn^2k}w\right)\)。
代码(好像要加个 O3,然后过不过还要看评测只因心情。不过 ccf 只因应该更快吧,当时我查分的时候发现全是 PE 没有 T 的):
#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
#define mp make_pair
#define X first
#define Y second
const int inf=0x3f3f3f3f;
const int N=500,K=5000;
int n,m,k;
int d[N+1];
vector<pair<pair<int,int>,pair<int,int> > > ans;
void use(int x,int y=0){
m--;
if(d[x]>=k){
ans.pb(mp(mp(x,k),mp(0,0)));
d[x]-=k;if(!d[x])n--;
}
else{
int now=k;
ans.pb(mp(mp(x,d[x]),mp(0,0)));
now-=d[x],d[x]=0,n--;
ans.back().Y=mp(y,now);
d[y]-=now;if(!d[y])n--;
}
}
void m_lss_n(){
pair<int,int> mn(inf,inf),mx1(0,0),mx2(0,0);
for(int i=1;i<=N;i++)if(d[i]){
mn=min(mn,mp(d[i],i));
if(mp(d[i],i)>mx1)mx2=mx1,mx1=mp(d[i],i);
else if(mp(d[i],i)>mx2)mx2=mp(d[i],i);
}
if(mx2.X>k)use(mn.Y,mx2.Y);
else use(mx2.Y,mx1.Y);
}
void m_eq_n(){
pair<int,int> mn(inf,inf),mx(0,0);
for(int i=1;i<=N;i++)if(d[i])mn=min(mn,mp(d[i],i)),mx=max(mx,mp(d[i],i));
use(mn.Y,mx.Y);
}
void m_grt_n(){
pair<int,int> mx(0,0);
for(int i=1;i<=N;i++)if(d[i])mx=max(mx,mp(d[i],i));
use(mx.Y);
}
void deal(){
while(m){
if(m<n)m_lss_n();
else if(m==n)m_eq_n();
else m_grt_n();
}
}
bitset<2*N*K+1> dp[N+1];
void mian(){
ans.clear();
cin>>n>>m>>k;
memset(d,0,sizeof(d));
for(int i=1;i<=n;i++)cin>>d[i];
if(m>=n-1)deal();
else{
for(int i=0;i<=n;i++)dp[i].reset();
dp[0].set(n*k);
for(int i=1;i<=n;i++){
int x=d[i]-k;
if(x<0)dp[i]=dp[i-1]|dp[i-1]>>-x;
else dp[i]=dp[i-1]|dp[i-1]<<x;
}
vector<pair<int,int> > v1,v2;
int now=n*k-k;
if(!dp[n][now])return puts("-1"),void();
for(int i=n;i;i--){
if(dp[i-1][now])v1.pb(mp(i,d[i]));
else now-=d[i]-k,v2.pb(mp(i,d[i]));
}
memset(d,0,sizeof(d));
for(int i=0;i<v1.size();i++)d[v1[i].X]=v1[i].Y;
n=v1.size(),m=n-1,deal();
memset(d,0,sizeof(d));
for(int i=0;i<v2.size();i++)d[v2[i].X]=v2[i].Y;
n=v2.size(),m=n-1,deal();
}
for(int i=0;i<ans.size();i++)
if(ans[i].Y.Y)printf("%d %d %d %d\n",ans[i].X.X,ans[i].X.Y,ans[i].Y.X,ans[i].Y.Y);
else printf("%d %d\n",ans[i].X.X,ans[i].X.Y);
}
int main(){
int testnum;
cin>>testnum;
while(testnum--)mian();
return 0;
}