洛谷 P6775 - 製作菜品
第 100 篇部落格祭!
話說這題為啥是個黑題啊,感覺我能獨立 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\)
然後考慮 \(m>n\)。顯然有 \(mx>k\)。直接用 \(mx\) 配出一個菜,還用不完,只有 \(m\) 減一。就這樣一直減減減,直到 \(m=n\) 為止。
然後考慮 \(m=n-1\)
證明:反證法。假設不能,則 \(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\)
所以只需要優先用 \(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\leq 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);
for(int i=0;i<ans.size();i++)
}
int main(){
int testnum;
cin>>testnum;
while(testnum--)mian();
return 0;
}