P2157 [SDOI2009]学校食堂
貌似我这个做法就是用了队列,无脑一点。
首先分析一下题目性质。如果\(x\)是打完饭的人当中排队最靠后的,那么1到x-8这些人肯定都打完饭了。定义状态\(f_{i,j,k}\)表示标号最大的打到饭的是\(i\)(有点拗口),最后一个打饭的是\(i-k\),\(k\)的范围是0到7,\(i-1\)到\(i-7\)的状态是\(j\),需要的最少的时间。
这种状态定义没有后效性(想想为什么),只是不好确定转移的顺序。其实直接bfs就行了,前面说的队列出场了。。
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
#define forg(i,x) for(int i=first[x];i;i=nxt[i])
#define uu unsigned
#define fi first
#define se second
#define od(x) ((x)&1)
#define ev(x) (od(x)^1)
#define mi2(x) (1<<(x))
#define gw(x,j) ((x)>>(j)&1)
//取出二进制数的某一位
#define fre(x) freopen(#x".in","r",stdin),freopen(#x".out","w",stdout)
const int inf=0x3f3f3f3f;
int n,t[1003],b[1003],dp[1003][8][1<<7],lim;
int nn;
struct dul{
int a,b,c;
}q[2000003];
inline bool vad1(int i,int zi,int k){
//if(b[i-k]<k)return 0;
for(int ii=k+1;ii<=7;++ii)if(!gw(zi,ii-1)&&b[i-ii]<ii-k)return 0;
return 1;
}
inline bool vad2(int i,int zi,int k){
if(i+k>n)return 0;
for(int ii=1;ii<=7;++ii)if(!gw(zi,ii-1)&&b[i-ii]<k+ii)return 0;
for(int ii=1;ii<k;++ii)if(b[i+ii]<k-ii)return 0;
return 1;
}
inline void pr2(int zi){for(int k=1;k<=7;++k)printf("%d",gw(zi,k-1));}
int main(){
lim=(1<<7)-1;
int cases;scanf("%d",&cases);while(cases--){
scanf("%d",&n);for(int i=1;i<=n;++i)scanf("%d%d",t+i,b+i);
memset(dp,0x3f,sizeof(dp));
int qh=1,qt=0;
q[++qt]=(dul){0,0,lim},dp[0][0][lim]=0;
while(qh<=qt){
int i=q[qh].a,j=q[qh].b,zi=q[qh].c;++qh;
for(int k=1;k<=7;++k)if(!gw(zi,k-1)&&vad1(i,zi,k)){
int &f=dp[i][k][zi|mi2(k-1)];
if(f==inf)q[++qt]=(dul){i,k,zi|mi2(k-1)};
f=min(f,i==0?0:dp[i][j][zi]+(t[i-j]^t[i-k]));
}
for(int k=1;k<=7;++k)if(vad2(i,zi,k)){
int zz=(zi<<k)|mi2(k-1);zz&=lim;
int &f=dp[i+k][0][zz];
if(f==inf)q[++qt]=(dul){i+k,0,zz};
f=min(f,i==0?0:dp[i][j][zi]+(t[i-j]^t[i+k]));
}
}
int ans=inf;
for(int k=0;k<=7;++k)ans=min(ans,dp[n][k][lim]);
printf("%d\n",ans);
}
return 0;
}