1. 程式人生 > 其它 >【題解】[SDOI2009]學校食堂

【題解】[SDOI2009]學校食堂

很早很早之前就覺得是一個麻煩的題目也感覺很簡單不想寫……結果今天寫的時候發現之前的想法假了(所以不要口胡題目不寫程式碼)

[SDOI2009]學校食堂

\(\text{Solution:}\)

看到 \(b[i]\leq 7\) 的資料範圍先想到狀壓。

考慮狀壓一個人後面的人的打飯情況,那麼可以設 \(f[i][S]\) 表示前 \(i\) 個人已經打完飯,\(i+1\to i+b[i]\) 的打飯情況是 \(S\) 的最小時間。

但是會發現一件很棘手的事情:當我們去用 \(S\) 轉移的時候,把一個人放到前面影響的不僅僅是 \(i\) 一個位置,更有 \(i\to pos\) 之間的所有位置。

也就是說,如果我想要把一個位置提前,那麼它必須滿足所有還沒有打飯的人的容忍度,這個東西是單調遞減的。

所以我們可以考慮在 \(dp\) 的時候來維護一個範圍,如果當前列舉的人已經超過合法範圍了,那我們就直接 \(pass.\)

繼續考慮,如果我們讓一個人 \(j\) 到前面去,代價應該是什麼呢?

由於這個狀態的設計,我們發現我們沒有辦法獲得上一次打飯的人的編號。於是,這個編號應該也作為 \(dp\) 裡面的一個維度。

發現空間很大怎麼辦,考慮到這個人和 \(i\) 的差別不超過 \(8\) 於是我們可以考慮用一個 “位移長度” \(k\) ,以 \(i+k\) 代表上一次打飯的人的編號。這樣就解決了空間的問題。

繼續考慮,發現如果這樣設計狀態 對於第一個位置 \(1\) 來說,\(i\) 已經打完飯的最優解實際上是不是很好確定的。因為第一道菜不需要時間。於是我們稍微改一下方程:

\(f[i][j][k]\) 表示前 \(i-1\) 個人全都打完飯了,當前人們的打飯狀態為 \(j,\) 上一次打飯的人的編號是 \(i+k\) 的最小時間。

發現這個 \(k\) 實際上是可以到 \(-8\) 的,因為可以把一個編號靠後的人拿到前面去,這樣上一次打飯的人和 \(i\) 這一層就有可能差出 \(8.\)

為了解決這個問題,考慮把陣列整體平移一下,加上一個 \(8\) 就行了。

那麼,考慮轉移:如果當前 \(i\) 已經打飯了,觀察一下這個狀態 顯然對於 \(f[i+1][j>>1][k+7]\) 這個狀態,\(f[i][j][k+8]\) 是和它等價的。

於是直接轉移即可。

另一種轉移是考慮列舉一個人去打飯,這個時候我們需要同時維護一下一個容忍度的上界,轉移的時候還需要特別注意是不是第一個人。

關於初始化 \(f[1][0][7]=0\) 是指上一個打飯的人是 \(1\) 前面的一個人,且 \(0\) 後面的人都沒有打飯。

轉移就是:

\[\text{f[i][j|(1<<p)][p+8]=min\{dp[i][j][k+8]+(t[i+k] xor t[i+p])\} } \]

這裡是異或的原因是\(\text{a or b-a and b=a xor b}\)

最後統計答案要統計 \(n+1\) 的,因為狀態設計的是 \(i-1\) 全部填滿。

#include<bits/stdc++.h>
using namespace std;
const int dyx=0x3f3f3f3f;
const int MAXN=2e3+10;
int f[MAXN][1<<9][20];
int n,t[MAXN],b[MAXN];
inline int Min(int x,int y){return x<y?x:y;}
void solve(){
	scanf("%d",&n);
	for(int i=1;i<=n;++i)scanf("%d%d",&t[i],&b[i]);
	memset(f,dyx,sizeof f);f[1][0][7]=0;
	for(int i=1;i<=n+1;++i)
		for(int j=0;j<(1<<8);++j)
			for(int k=-8;k<8;++k){
				if(f[i][j][k+8]!=dyx){
					if(j&1)f[i+1][j>>1][k+7]=Min(f[i+1][j>>1][i+7],f[i][j][k+8]);
					else{
						int R=dyx;
						for(int p=0;p<8;++p){
							if(j>>p&1)continue;
							if(i+p>R)break;
							R=Min(R,i+p+b[i+p]);
							f[i][j|(1<<p)][p+8]=Min(f[i][j|(1<<p)][p+8],f[i][j][k+8]+(k+i>0?(t[i+k]^t[i+p]):0));
						}
					}
				}
			}
	int ans=dyx;
	for(int i=0;i<=15;++i)ans=Min(ans,f[n+1][0][i]);
	cout<<ans<<endl;
	for(int i=1;i<=n;++i)t[i]=b[i]=0;
}
int main(){
	int T;
	cin>>T;
	while(T--){
		solve();
	}
	return 0;
}