bzoj1226 [SDOI2009]学校食堂Dining (状压DP)

83 篇文章 0 订阅
53 篇文章 0 订阅

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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值