[SDOI2009]学校食堂(状压dp)

传送门

B和C都很小,于是想到状压。

但状压的难点在于一个人吃饭的顺序会和其前面的人以及后面的人都有关。

幸而最多只会和后七个人有关,我们考虑把自己以及后七个人压成一个状态。

然后我们要知道上一次吃饭的人是谁,所以也存入状态中。只和前后七个人有关。

设dp[i][j][k]为第1个人到第i-1个人已经打完饭,包括i自己及后7个人的状态最后吃饭的人是i+k的最小时间(k:-8~7--->负下标处理为0~15)。

然后是如何转移:

1.这一次是i自己吃饭

dp[i][j][k+8]--->dp[i+1][j>>1][k+7]

j>>1表示状态的人整体往后移一位(因为这次 i 吃饭了)。

2.这一次是i后面的人吃饭

那么我们枚举后面可以吃饭的人,让他们先吃饭。

注意这是有一定范围的,不仅i本身有范围,后面的人也有范围,所以取min。

lim=min(lim,i+p+b[i+p]);(p是枚举的后面的人)

于是从上一次吃饭的人i+k转移到i+p

dp[i][j|(1<<p)][p+8]=min(dp[i][j|(1<<p)][p+8],dp[i][j][k+8]+((i+k)?(di[i+k]^di[i+p]):0));

然后是总的代码

#include<bits/stdc++.h>
#define N 1003
#define LL long long
#define mod 1000000007
#define INF 1061109567
using namespace std;
int read()
{
    int x=0,f=1;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    return x*f;
}
int T;
int n;
int di[N],b[N];
int dp[N][1<<8][16];//第1个人到第i-1个人已经打完饭,包括i自己及后7个人的状态最后吃饭的人是i+k,k:-8~7--->0~15
void work()
{
    n=read();
    for(int i=1;i<=n;++i)
      di[i]=read(),b[i]=read();
    memset(dp,63,sizeof(dp));
    //printf("%d
",dp[1][1][1]); 
    dp[1][0][7]=0;
    for(int i=1;i<=n;++i)
      for(int j=0;j<(1<<8);++j)
        for(int k=-8;k<=7;++k)
        {
            if(dp[i][j][k+8]==INF)continue;
            if(j&1)dp[i+1][j>>1][k+7]=min(dp[i+1][j>>1][k+7],dp[i][j][k+8]);
            else
            {
                int lim=INF;
                for(int p=0;p<=7;++p)
                {
                    if((j>>p)&1)continue;
                    if(i+p>lim)break;
                    lim=min(lim,i+p+b[i+p]);
                    dp[i][j|(1<<p)][p+8]=min(dp[i][j|(1<<p)][p+8],dp[i][j][k+8]+((i+k)?(di[i+k]^di[i+p]):0));
                }
            }
        }
    int ans=INF;
    for(int k=0;k<=8;++k)
      ans=min(ans,dp[n+1][0][k]);
    printf("%d
",ans); 
}
int main()
{
    T=read();
    while(T--)work();
}
View Code
原文地址:https://www.cnblogs.com/yyys-/p/11844709.html