1. 程式人生 > >【學術篇】SDOI2009 學校食堂

【學術篇】SDOI2009 學校食堂

min cnblogs pre 數組 con class cpp else tps

傳送門~

題目大意

先分析\((x\ or\ y)-(x\ and\ y)\), 就是\(x\)\(y\)中存在的1減去\(x\)\(y\)中相同的1 *那不就是\(x\ xor\ y\)麽←_←*

給定\(n\)個人, 確定一個排列, 使得不存在\(i+b_i\)\(i\)之前, 並最小化\(\sum_{i=2}^{n}t_i\ xor\ t_{i-1}\).

題目分析

\(1\leqslant b_i\leqslant7\), 數據範圍一眼狀壓...
但是具體怎麽定義狀態呢?
假如說(最一般的想法)\(f_{i,j}\)表示到第\(i\)個人的時候(前\(i-1\)個人已經打完飯), 後面(包括他)的打飯集合為\(j\)

(0表示沒打 1表示打了)..
但是推的時候要涉及到上一個人的打飯狀態...
而上一個打飯的人不一定是\(i-1\)
所以我們還要記錄一下上一個打飯的人...

定義狀態\(f_{i,j,k}\)表示前\(i-1\)個人都已經打完飯, \(i\sim i+7\)的打飯集合為\(j\), 上一個打飯的是\(i+k\).
很顯然\(k=-8\sim 7\). 而由於c++數組的尿性, 我們要\(k+8\)再存

然後就是考慮遞推了.

  • 初始化的話 因為要找最小值 全都賦值為\(\infty\)...
    邊界條件\(f_{1,0,-1}=0\)顯然.

  • 首先如果\(j\&1\neq 0\), 說明第\(i\)
    個人已經打飯了, 後面的人就不會跑到他前面...
    我們發現這個狀態和 \(f_{i+1,j>>1,k-1}\) (第\(i+1\)個人打飯, 集合為去掉\(i\)後的狀態, 最後一個打飯的人是\((i+1)+(k-1)\)是一樣的.. 可以直接轉移過去.
  • 如果\(j\&1=0\)呢? 說明第\(i\)個人還沒有打飯. 那就不能轉移到\(f_{i+1,?,?}\)了.
    我們就要從後面枚舉一個人, 讓他去打飯.
    我們可以\(1\sim 7\)枚舉\(l\), 目標狀態就是\(f_{i,j|(1<<l),l}\)...
    於是就出現了\(f_{i,j|(1<<l),l}=min\{f_{i,j,k}+t_{i+k}\ xor\ t_{i+l}\}\)
    ...
    但是要註意第一道菜是不需要時間的, 所以要特判\(i+k=0\)的情況...
    然後要註意的就是枚舉的這個人不能引起別人的憤怒...
    所以要維護一下能忍耐的範圍...
    一旦不能忍耐了, 那就直接break掉就行.. 因為後面的更不行了...
  • 最後從\(f_{n+1,0,?}\)裏面找個最小的作為\(ans\)就好了~

這樣就做完了.

代碼:

這種枚舉變量個數多的dp用的tab縮進真是美如畫..

#include <cstdio>
#include <cstring>
const int N=1002;
int f[N][260][17],t[N],b[N];
inline int gn(int a=0,char c=0){
    for(;c<'0'||c>'9';c=getchar());
    for(;c>47&&c<58;c=getchar())a=a*10+c-48;return a;
}
inline int min(const int &a,const int &b){return a<b?a:b;}
void work(){
    memset(f,0x3f,sizeof(f)); int n=gn();
    for(int i=1;i<=n;++i) t[i]=gn(),b[i]=gn();
    f[1][0][7]=0;
    for(int i=1;i<=n;++i)
        for(int j=0;j<256;++j)
            for(int k=-8;k<8;++k)
                if(f[i][j][k+8]<1e9){
                    if(j&1) f[i+1][j>>1][k+7]=min(f[i+1][j>>1][k+7],f[i][j][k+8]);
                    else{
                        int r=1e9;
                        for(int l=0;l<8;++l)
                            if(!(j&(1<<l))){
                                if(i+l>r) break;
                                if(i+l+b[i+l]<r) r=i+l+b[i+l];
                                f[i][j|(1<<l)][l+8]=min(f[i][j|(1<<l)][l+8],f[i][j][k+8]+(i+k?(t[i+k]^t[i+l]):0));
                            }
                    }
                } int ans=1e9;
    for(int i=-8;i<8;++i) ans=min(ans,f[n+1][0][i]); printf("%d\n",ans);
}
int main(){
    int T=gn();
    while(T--)work();
}

註意事項

註意事項應該都說過了...
可能要提醒的就是多組數據, 每次記得清理f數組...
然後就是該有的特判都不要少..
一定時刻記得第三維要+8哦~
完結撒花~

【學術篇】SDOI2009 學校食堂