P2157 [SDOI2009]学校食堂

传送门

做菜主要是按时间顺序,所以可以考虑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;
}
原文地址:https://www.cnblogs.com/LLTYYC/p/9699213.html