bzoj1226 [SDOI2009]学校食堂Dining
原题地址:http://www.lydsy.com/JudgeOnline/problem.php?id=1226
题意:
T组数据。
有N个人排队吃饭,每个人有a,b两个属性,要以某种顺序为这N个人做菜,考虑目前这个人是i,他的前一个人j,那么让他吃饭的时间就是(a[i] | a[j]) – (a[i] & a[j])(如果是第一个吃饭的人,那么代价就是0),按原本顺序队伍中的第i 个同学,最多允许紧跟他身后的Bi 个人先拿到饭菜(就是说原本顺序的人 k,k > i + b[i] ,不能在事i之前完成)。
求让N个人吃上饭的最小时间。
数据范围
对于30%的数据,满足1 ≤ N ≤ 20。
对于100%的数据,1 ≤ T ≤ 5,1 ≤ N ≤ 1,000,0 ≤ Ai ≤ 1,000,0 ≤ Bi ≤ 7
题解:
可以看到部分数据1 ≤ N ≤ 20,对于这种情况,能够想象出的就是用状压来记录,dp[i][s]表示状态为s的人吃过了,当前吃的人是i。
由于给了B[ ],又发现0 ≤ Bi ≤ 7,可以想到类似的状压。
dp[i][s][j]表示,处理到第i位,1~i-1的人都吃过了,对于原来排队在i之后的7个人加上i本身,共8位,此时的状态为s,上一个吃饭的人是j,的最小时间。 但是1 ≤ N ≤ 1000,空间开不下。
观察可知由于0 ≤ Bi ≤ 7,那么上一个吃饭的人的范围就只是 [ i-8,i+7 ](是j的下一个的下7个、i+7个在i前面吃饭)
于是j表示上一个吃饭的与i的相对位置[-8,+7],因为有负数。统一加个8。
对于dp[i][s][j],可以得到转移:
若第i位已经吃过(s&1>0) ,直接转移到下一位
dp[i+1][s>>1][j-1]=min(dp[i+1][s>>1][j-1],dp[i][s][j])
否则枚举s中没吃过的人k,
dp[i][s|(1<< k)][k]=min(dp[i][s|(1<< k)][k],dp[i][s][j]+w(i+j,i+k))
注意每次更新枚举k的限制,保证对于这些人也满足在他前面的只有位置小于等于b的。
代码:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define LL long long
using namespace std;
const int N=1005;
const int inf=0x3f3f3f3f;
const int P=8;
const int top=(1<<8)-1;
int dp[N][(1<<8)+10][20];
int T,n,a[N],b[N];
int w(int x,int y)
{
if(x==0) return 0; else return a[x]^a[y];
}
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++) {scanf("%d%d",&a[i],&b[i]);b[i]=min(b[i],n-i);}
memset(dp,0x3f,sizeof(dp));
dp[1][0][-1+P]=0;
for(int i=1;i<=n;i++)
for(int s=0;s<=top;s++)
for(int j=-8;j<=7;j++)
{
if(dp[i][s][j+P]<inf)
{
if(s&1)
{ dp[i+1][s>>1][j-1+P]=min(dp[i+1][s>>1][j-1+P],dp[i][s][j+P]); continue;}
int lim=7;
for(int k=0;k<=lim;k++)
if(!((1<<k)&s))
{
dp[i][s|(1<<k)][k+P]=min(dp[i][s|(1<<k)][k+P],dp[i][s][j+P]+w(i+j,i+k));
lim=min(lim,k+b[i+k]);
}
}
}
int ans=inf;
for(int i=-8;i<=-1;i++) ans=min(ans,dp[n+1][0][i+P]);
printf("%d\n",ans);
}
return 0;
}