1. 程式人生 > 實用技巧 >洛谷 P6775 - 製作菜品

洛谷 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\)

入手。此時顯然有 \(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\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;
}