【题解】[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;
}
posted @ 2021-07-12 22:00  Refined_heart  阅读(65)  评论(0编辑  收藏  举报