NKOJ-3790 BZOJ-1226 [SDOI2009]學校食堂Dining

P3790【SDOI2009】學校食堂
時間限制 : - MS 空間限制 : 65536 KB
評測說明 : 1000ms
問題描述

小F 的學校在城市的一個偏僻角落,所有學生都只好在學校吃飯。學校有一個食堂,雖然簡陋,但食堂大廚總能做出讓同學們滿意的菜餚。當然,不同的人口味也不一定相同,但每個人的口味都可以用一個非負整數表示。由於人手不夠,食堂每次只能爲一個人做菜。做每道菜所需的時間是和前一道菜有關的,若前一道菜的對應的口味是a,這一道爲b,則做這道菜所需的時間爲(a or b)-(a and b),而做第一道菜是不需要計算時間的。其中,or 和and 表示整數逐位或運算及逐位與運算,C語言中對應的運算符爲“|”和“&”。學生數目相對於這個學校還是比較多的,吃飯做菜往往就會花去不少時間。因此,學校食堂偶爾會不按照大家的排隊順序做菜,以縮短總的進餐時間。雖然同學們能夠理解學校食堂的這種做法,不過每個同學還是有一定容忍度的。也就是說,隊伍中的第i 個同學,最多允許緊跟他身後的Bi 個人先拿到飯菜。一旦在此之後的任意同學比當前同學先拿到飯,當前同學將會十分憤怒。因此,食堂做菜還得照顧到同學們的情緒。現在,小F 想知道在滿足所有人的容忍度這一前提下,自己的學校食堂做完這些菜最少需要多少時間。

輸入格式

第一行包含一個正整數C,表示測試點的數據組數。
每組數據的第一行包含一個正整數N,表示同學數。
每組數據的第二行起共N行,每行包含兩個用空格分隔的非負整數Ti和Bi,表示按隊伍順序從前往後的每個同學所需的菜的口味和這個同學的忍受度。
每組數據之間沒有多餘空行。

輸出格式

包含C行,每行一個整數,表示對應數據中食堂完成所有菜所需的最少時間。

樣例輸入

2
5
5 2
4 1
12 0
3 3
2 2
2
5 0
4 0

樣例輸出

16
1

提示

對於第一組數據:同學1允許同學2或同學3在他之前拿到菜;同學2允許同學3在他之前拿到菜;同學3比較小氣,他必須比他後面的同學先拿菜…… 一種最優的方案是按同學3、同學2、同學1、同學4、同學5做菜,每道菜所需的時間分別是0、8、1、6及1。
【數據規模和約定】
對於30%的數據,滿足1 ≤ N ≤ 20。
對於100%的數據,滿足1 ≤ N ≤ 1,000,0 ≤ Ti ≤ 1,000,0 ≤ Bi ≤ 7,1 ≤ C ≤ 5。存在30%的數據,滿足0 ≤ Bi ≤ 1。
存在65%的數據,滿足0 ≤ Bi ≤ 5。
存在45%的數據,滿足0 ≤ Ti ≤ 130。

來源 SDOI2009

這怕是離偉大的共產注意不遠了

題解

首先講由數據得到的思路

從後面的人可以先吃 就可以很輕易地想到狀壓(這樣才能處理有一些人插隊吃飯的記錄問題)
但是題目的n的範圍…
但是這仍然阻止不了我們使用狀壓的心 Bi的範圍讓我們看到了希望

於是 在一個巧合之下 一股來自東方的神祕力量讓我定下了這個狀態

f[i][j][k]表示 第i個人和他後面不大於7個人當前吃飯狀態 爲 j 並且 當前狀態最後一個吃飯的人爲i+k 且k可以爲負數(這很重要)

j即爲對每個點的狀壓 j的二進制的第x位即表示 第i+x個人的吃飯情況(吃了1或者沒吃0)

而且對於狀態f[i] 必須保證前i-1個人全部完了

繼承

對於每一個f[i+1] 它都是從f[i]繼承過來的(也就是說是由f[i-1]推到的f[i])
繼承條件就是對於狀態f[i][j] 第i個人已經吃了飯了

繼承方程
對於f[i][j][k] 若j&1則i+1繼承 f[i+1][j>>1][k+1]=min(f[i+1][j>>1][k-1],f[i][j][k])

方程的具體含義 i+k = i+1 + k-1
且 j 中的每一位 到 i+1 的狀態中 都應該把第i個人給移出去(即操作j>>1)

此處就一些顧慮做出解釋
有些人可能會想到 萬一i吃完了i-1才吃呢

對於每一個f[i+1] 它都是從f[i]繼承過來的
所以 f[i]的狀態是一定會考慮 i 之後的人先吃飯的情況的
根據上述繼承的方程 f[i+1]在繼承時會將後面的人先吃飯的情況繼承下來

繼續插隊

對於無法繼承的情況 我們就進行繼續的討論(我們畢竟還是要把所有的情況討論完)

我們枚舉討論當前狀態j中所有沒吃飯的人 並且讓他們吃飯 然後更新狀態
吃飯方程
對於f[i][j][k] 若j&(1 << x)==0 且x<=stand 則 f[i][j+(1<< x)][x]=min(f[i][j+(1<< x)][x],f[i][j][k]+add(i+k,i+x))

方程含義
j&(1<<x)==0 表示 第i+x個人沒吃飯
stand表示當前可以吃飯的人的範圍(題目當中插隊人數的上限)
j+(1<<x) 爲 i+x 吃飯之後的狀態
add(i+k,i+x)表示i+x在i+k之後吃飯的時間花費

有那麼一點小細節可能要注意一下

stand是要更新的 更新的值就是討論到x位置所有沒吃飯的人的限制值(插隊人數)
因爲在你討論x的時候 就註定了前面那些沒吃飯的人在當前狀態以及之後的狀態都是吃不到飯的

add要分情況討論 第一個打飯的人是不花時間的

完成

狀壓裏頭細節很重要 錯一點就全錯了

附上對拍代碼(含思路)

//簡單註釋思路
#include <iostream>
#include <cstdio>
#include <cstring>
#define g(i,j,k) f[i][j][k+8]//因爲k+8想起很麻煩 所以直接替換掉 
#define inf 0x3f3f3f3f
using namespace std;

inline int input()//輸入優化 
{
    char c=getchar();int o;
    while(c>57||c<48)c=getchar();
    for(o=0;c>47&&c<58;c=getchar())o=(o<<1)+(o<<3)+c-48;
    return o;
}

int f[1023][267][16];//f[i][j][k]表示 第i個人 處在j狀態(狀態上的1代表已經吃飯 反之則沒有) 最後一個吃飯的人爲k 
/*
    開16的原因是因爲 當前的f[i][j]的狀態是從[i-1]繼承來的
    所以有可能存在最後一個吃飯的人在i之前(k<0)
    因此我們用前7個數字代表在i之前吃飯的人 後8個數字表示包括i在內的在i之後吃飯的人
*/ 
int a[1023],maxp[1023];

int mini(int a,int b,int c)
{
    if(a<=b&&a<=c)return a;
    if(b<=a&&b<=c)return b;
    return c;
}

int add(int x,int y){return (x==0)?0:a[x]^a[y];}//x==0表示當前吃飯的y是第一個人 所以做菜不花時間 

int main()
{
    //freopen("dining.in","r",stdin);
    //freopen("dining.out","w",stdout);
    int T=input();
    while(T--)
    {
        memset(f,inf,sizeof(f));
        int n=input(),res=inf;
        for(int i=1;i<=n;i++)a[i]=input(),maxp[i]=input();//a記錄題目中的t[i],maxp記錄最多被多少人插隊 
        g(1,0,-1)=0;
        for(int i=1;i<=n;i++)
        for(int j=0;j<(1<<8);j++)
        for(int k=-8;k<=7;k++)
        //以上爲隨意枚舉過程 注意 k可能是i之前的人 
        if(g(i,j,k)<inf)
        {
            if(j&1)g(i+1,j>>1,k-1)=min(g(i+1,j>>1,k-1),g(i,j,k));
            //如果當前這個人吃了飯(j&1==1) 那麼就可以直接傳遞狀態至下一個人討論(具體的可能會在題解中解釋) 
            else
            {
                int maxl=inf;//用於記錄可以下一個吃飯的人的範圍 
                for(int nt=0;nt<8;nt++)//枚舉沒有吃飯的人 
                if(!(j&(1<<nt))) 
                {
                    if(i+nt>maxl)break;
                    maxl=min(maxl,i+nt+maxp[i+nt]);//在繼續向後枚舉時必定會有當前這個人沒吃飯 所以要更新maxl 
                    g(i,j^(1<<nt),nt)=min(g(i,j,k)+add(i+k,i+nt),g(i,j+(1<<nt),nt));//當前這個人吃飯的情況 
                }
            }
        }
        for(int i=-8;i<=7;i++)res=min(res,g(n,1,i));//j=1 就表示當前n吃了飯了 飯局結束 
        printf("%d\n",res);
    }
}
發佈了79 篇原創文章 · 獲贊 15 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章