*题解:P2157 [SDOI2009] 学校食堂
解析
注意到 \(B_i \le 7\),考虑从这里入手。
考虑 \(N\le 8\) 的情况,此时当然可以直接暴搜,但我们来想一种看起来对正解有启发性的做法。不妨把暴搜时的状态记录下来,具体需要一个集合 \(S\) 表示当前已经打了饭的同学,一个正整数 \(k\) 表示最后打饭的同学。枚举最后打饭的同学,我们就得到了一个状压 dp 转移式:
其中 \(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\) 个同学已被安排打饭时,我们有:
其次,当区间内仍有未安排打饭的学生时,使用类似小规模数据的转移。但是要注意,由于 \(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;
}

浙公网安备 33010602011771号