SDOI 2009 學校食堂

洛谷 P2157 [SDOI2009]學校食堂

洛谷傳送門php

JDOJ 1924: [SDOI2009]學校食堂Dining

JDOJ傳送門數組

Description

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

Input

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

Output

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

Sample Input

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

Sample Output

16 1ip

HINT

對於第一組數據:
同窗1容許同窗2或同窗3在他以前拿到菜;同窗2容許同窗3在他以前拿到菜;同窗3比較小氣,他必須比他後面的同窗先拿菜……
一種最優的方案是按同窗三、同窗二、同窗一、同窗四、同窗5作菜,每道菜所需的時間分別是0、八、一、6及1。get

【數據規模和約定】
對於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。input

題解:

2019.11.6模擬賽T1 爆蛋場string

由於蒟蒻對狀壓不是很熟悉,因此一開始根本沒往那邊想,發現能夠暴力生成全排列看看合不合法。而後持續更新答案。預計30\(pts\)可是寫掛了

那麼講一下這個正解。(由於蒟蒻才學狀壓,因此不少地方都有初學者的痕跡,請大佬們海涵)

一開始遲遲沒法理解這道題爲啥能用\(DP\),由於這道題對於每個人來說,他何時打飯既和他前面的人有關(能不能容忍他打飯),也跟後面的人有關(他能不能容忍他們打飯)。在個人印象中,這就叫後效性,是不符合\(DP\)的條件的。

因此須要用狀態壓縮解決

是的,狀態壓縮就是把狀態化成二進制數存進數組中,而後保證當前狀態下轉移時是無後效性的。(多麼妙啊)

那麼咱們考慮狀態的設計思路:

首先,這個東西是必定要與枚舉到的人有關的,因此開一維存當前枚舉到哪一個人。

其次,由於這個東西的轉移邊界是容忍後面的人吃沒吃飯。那麼就會有一維存狀態:表示\(i\)\(i\)後面\(7\)我的到底吃沒吃飯。

最後,由於這個轉移還和前面的人能不能忍你先吃飯有關。因此還須要開一維維護這個關係,存儲上一個打飯的人到當前這我的的相對距離。

綜上,設置:

\(dp[i][st][k]\)爲:當第\(1\)到第\(i-1\)我的所有吃完飯後,\(i\)\(7\)我的(把\(i\)也算上)吃沒吃飯,\(i\)前面吃飯的人和\(i\)之間相對距離爲\(k\)時的最小价值。(設\(0\)爲沒吃過,\(1\)爲吃過)

那麼答案就應該是\(\min\{dp[n+1][0][k]\}\quad (k\in[0,8])\)。這裏的\(k\)的取值是由於數組不能開負數,因此把整個\(k\)的區間從\([-8,0]\)挪到\([0,8]\)了。

而後咱們考慮怎麼去轉移。

注意:這裏的狀態(是以十進制存儲的)拆成二進制(有8位)後,最後面那位表示的不是最後的那我的,偏偏相反,是第一我的(即當前的那個\(i\)))

能夠想到的是,對於每個人,狀態轉移首先須要看這我的到底吃沒吃,那麼咱們分兩種狀況討論:

第一種:這個\(i\)已經吃了。這種狀況下,\(st\&1\)應該爲真。那麼就能夠直接去推下一我的,轉移方程爲:
\[ dp[i+1][j>>1][k+7]=\min(dp[i+1][j>>1][k+7],dp[i][j][k+8]); \]
第二種:這個\(i\)尚未吃。這種狀況下,是沒有辦法轉移到\(i+1\)的,由於咱們的狀態設置的是前面的人都已經打完飯了(以排除後效性)。因此咱們就要枚舉狀態來選擇後面的7我的(固然包括本身)誰先打飯。

可是,這裏須要細考慮一下:不是後面的7我的中的全部人都是能夠打飯的。就好比後面的第\(7\)我的,若是後面的第一我的的忍耐度很小,一點都不能容忍本身後面的人先打,那麼這個第\(7\)人就是選不了的。(沒辦法攤上暴躁同窗就這樣)

因此咱們還須要在枚舉的同時判斷是否合法。

開一個變量\(limit\)儲存目前可行的最大範圍,隨着枚舉的繼續,這個範圍要麼不動,要麼縮小。因此若是當前枚舉到的人超過了這個範圍,那麼他以後的全部人也都超出了這個範圍,直接\(break\)掉就好。

這個時候的轉移方程是:
\[ dp[i][j|(1<<h)][h+8]=\min(dp[i][j|(1<<h)][h+8],dp[i][j][k+8]+val[now]) \]
其中\(val[now]\)表示作這道菜的須要的時間。

(這裏還有個性質:a|b-a&b==a^b)

因此就得出了完整的思路。

以及完整的代碼:

#include<stdio.h>
#include<string.h>
#define INF 0x3f3f3f3f
#define min(a,b) a<b?a:b
int n,ans,limit;
int t[1010],b[1010];
int dp[1010][1<<8][20];
//dp[i][st][k]表示當前爲i人、其後狀態爲st、
//前一個吃飯的人離i距離爲k時須要的最少時間。
//即上一個吃飯的人爲i+k-8
char *p1,*p2,buf[100000];
#define nc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++)
int read()
{
    int x=0,f=1;
    char ch=nc();
    while(ch<48){if(ch=='-')f=-1;ch=nc();}
    while(ch>47)    x=(((x<<2)+x)<<1)+ch-48,ch=nc();
    return x*f;
}
int main()
{
    int T;
    T=read();
    while(T--)
    {
        n=read();
        for(int i=1;i<=n;i++)
            t[i]=read(),b[i]=read();
        memset(dp,INF,sizeof(dp));
        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)
                    {
                        if(j&1)
                            dp[i+1][j>>1][k+7]=min(dp[i+1][j>>1][k+7],dp[i][j][k+8]);
                        else
                        {
                            limit=INF;
                            for(int h=0;h<=7;h++)
                                if(!((j>>h)&1))
                                {
                                    if(i+h>limit)
                                        break;
                                    limit=min(limit,i+h+b[i+h]);
                                    dp[i][j|(1<<h)][h+8]=min(dp[i][j|(1<<h)][h+8],dp[i][j][k+8]+(i+k?(t[i+k]^t[i+h]):0));
                                }
                        }
                    }
        ans=INF;
        for(int k=0;k<=8;k++)
            ans=min(ans,dp[n+1][0][k]);
        printf("%d\n",ans);
    }
    return 0;
}
相關文章
相關標籤/搜索