做菜主要是按时间顺序,所以可以考虑DP
但是可能后面的人会先打饭
可以发现同学最多只能让后面的第7个同学先打饭
可以从这里入手考虑问题
把每8个一起的同学看成一个状态
在他们之前的人都已经打好饭了
想象一个从左往右的队列
从1~i-1 的同学都打完饭了
然后需要知道的状态是 i~i+7 共8个同学的总状态
显然如果第 i 个同学已经打好了
那么 1~i 的同学都好了
所以可以转到下一个 i
设 f [ i ] [ j ] 表示从1~i-1 的同学都好了,第 i~i+7 的总状态为 j 时需要的最少时间
那么 如果 j&1 ==1 说明第 i 个同学已经好了
这时可以转到 f[ i+1 ] [ j>>1 ] (显然)
但是具体转的时候发现:
不知道上一个打饭的人是谁,就不知道这次需要多少时间...
但是可以发现
上一个打饭的人肯定在 i-8 ~ i+7 之间(不然肯定有同学不能容忍了)
所以多开一维
设 f [ i ] [ j ] [ k ] i 和 j 含义不变, k 表示上一个打饭的人是 i + k (-8<=k<=7,存的时候要加8)
那么 如果 j&1 ==1 f[ i ] [ j ] [ k ] 就可以转到 f [ i+1 ] [ j>>1 ] [ k-1 ] (注意这时并没有人打饭,只是两个状态表示的含义相同,可以直接转移)
然后考虑给一个人打饭
枚举 i ~ i+7 的人 h(h是与 i 的相对距离,和 k 一样)
那么 f [ i ] [ j ] [ k ] 可以转到 f [ i ] [ j|(1<<h) ] [ h ]
并且增加的时间是 (a[ i+k ]|a[ i+h ]) - (a[ i+k ]&a[ i+h ]) = a[ i+k ] ^ a[ i+h ]
注意判断 h 的合法性
显然一个同学如果打过饭了就不能再打 (j&(1<<h) != 1)
还要注意左边同学的容忍度 (j&(1<<l)!=1 && h-l<=b[ i+l ] && i<=l<h)
然后就可以转移了
最后在 f [ n+1 ] [ 0 ] [ i ] 之间取 min 就好了
#include<iostream> #include<cstdio> #include<cmath> #include<algorithm> #include<cstring> using namespace std; const int N=2007,INF=0x3f3f3f3f; int T,n,a[N],b[N]; int f[N][307][17]; inline bool pd(int &i,int &j,int &h)//判断合法性 { if(j&(1<<h)) return 1; for(int l=0;l<h;l++) if(h-l>b[i+l]&& !(j&(1<<l)) ) return 1; return 0; } int main() { cin>>T; while(T--) { //f[i][j][k] -> f[i+1][j>>1][k-1] ( j&1 && k ) //f[i][j][k] -> f[i][j&(1<<h)][h]+(a[i+k]^a[i+h]) ( !j&(1<<h) && ( h-l<=b[i+l] (!j&(1<<l)&&0<=l<h) ) ) memset(f,INF,sizeof(f)); for(int i=1;i<=7;i++) f[1][0][i]=0; cin>>n; for(int i=1;i<=n;i++) scanf("%d%d",&a[i],&b[i]); for(int i=1;i<=n;i++) for(int j=0;j<(1<<8);j++) for(int k=-8;k<8;k++) if(f[i][j][k+8]!=INF) { if(j&1 && k!=-8) f[i+1][j>>1][k+7]=min(f[i+1][j>>1][k+7],f[i][j][k+8]); for(int h=0;h<8;h++) { if(pd(i,j,h)) continue; f[i][j|(1<<h)][h+8]=min(f[i][j|(1<<h)][h+8],f[i][j][k+8]+ (i+k>0/*注意第一个同学不用时间*/ ? (a[i+k]^a[i+h]) : 0) ); } } int ans=INF; for(int i=0;i<16;i++) ans=min(ans,f[n+1][0][i]); printf("%d ",ans); } return 0; }