*题解:P2157 [SDOI2009] 学校食堂

原题链接

解析

注意到 \(B_i \le 7\),考虑从这里入手。

考虑 \(N\le 8\) 的情况,此时当然可以直接暴搜,但我们来想一种看起来对正解有启发性的做法。不妨把暴搜时的状态记录下来,具体需要一个集合 \(S\) 表示当前已经打了饭的同学,一个正整数 \(k\) 表示最后打饭的同学。枚举最后打饭的同学,我们就得到了一个状压 dp 转移式:

\[f_{S\cup \{k\},k}=\min_{j\in S} \left(f_{S,j} + g(j,k),f_{S\cup \{k\},k}\right) \]

其中 \(g(j,k)\) 表示前一道菜为 \(j\) 时,做第 \(k\) 道菜所需时间,即 \((T_j \operatorname{or} T_k)−(T_j\operatorname{and} T_k)\),即 \(T_j \operatorname{xor} T_k\)

当然这个转移是有条件的:\(S\) 中顺序最靠后的同学的顺序不能超过 \(k + B_{k}\)

这样,我们就得到了一个在小范围数据下时间复杂度为 \(O(2^N \cdot N^2)\) 的做法。

接下来尝试扩展。考虑 \(N = 9\) 的情况,这时由于 \(B_1 \le 7\),第 \(9\) 个同学必定无法插队到第 \(1\) 个同学前面去,也即无法影响到第 \(1\) 个同学。换个角度思考,当我们有机会安排第 \(9\) 个同学打饭时,可以假设第 \(1\) 个同学必定已经安排好打饭位置了。故将状态多加一维,设 \(f_{i,S,k}\) 表示考虑到第 \(i\) 个同学,前 \(i - 8\) 个同学安排好了打饭位置,\([i-7,i]\) 之间的同学打饭顺序是否安排的状态为 \(S\),当前最后打饭的同学为 \(k\) 时的答案。

首先,第 \(i - 7\) 个同学已被安排打饭,即前 \((i + 1) - 8\) 个同学已被安排打饭时,我们有:

\[f_{i + 1,S \setminus \{i-7\},k}=f_{i,S,k} \]

其次,当区间内仍有未安排打饭的学生时,使用类似小规模数据的转移。但是要注意,由于 \(N > 8\),所以对于原有的 \(k\in [i-7,i]\),还需要从 \([i - 15,i - 8]\) 的同学最后被安排打饭的状态中转移(考虑前面的同学往后走,后面的同学往前走的情况)。

时间复杂度 \(O(N\cdot 2^B\cdot B^2)\)

代码

#include <bits/stdc++.h>
using namespace std;
const int N = 1000 + 5,M = 8;
int f[N + 10][(1 << M)][15 + 2],v[N],b[N];
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int T;
	cin>>T;
	while(T--){
		int n;
		cin>>n;
		memset(f,127,sizeof(f));
		for(int i=1;i<=n;i++){
			cin>>v[i]>>b[i];
		}
		int c = min(15,n),t = min(8,n);
		f[t][0][t] = 0;
		int res = 2e9;
		for(int i=t;i<=n;i++){
			for(int j=0;j<(1 << t);j++){
				int mn = -1;
				for(int k=t - 1;k>=0;k--){
					if(j & (1 << k)){
						mn = i - (t - k - 1);
						break;
					}
				} 
				for(int l=min(i,c);l>=0;l--)if(l >= t || (j & (1 << (t - l - 1)))){
					for(int k=min(i,t - 1);k>=0;k--)if(l != k){
						int p = t - k - 1,q = t - l;
						if(!(j & (1 << p)) && i - k + b[i - k] >= mn){
							f[i][j | (1 << p)][k] = min(f[i][j | (1 << p)][k],f[i][j][l] + (i - l != 0) * (v[i - l] ^ v[i - k]));
						}
					}
				}
				if(j & 1){
					for(int k=0;k<c;k++){
						f[i + 1][j >> 1][k + 1] = f[i][j][k];
					}
				}
			}
		}
		for(int i=1;i<=c;i++){
			res = min(res,f[n + 1][((1 << t) - 1) >> 1][i]);
		}
		cout<<res<<'\n';
	}
	return 0;
}
posted @ 2026-04-17 00:02  yutar  阅读(12)  评论(0)    收藏  举报