P2157 [SDOI2009]學校食堂 狀壓DP
阿新 • • 發佈:2020-10-20
題意:
有點複雜,自行瀏覽吧 題目連結
分析:
我們發現DP轉移時需要記錄以下幾個資訊:
打飯佇列的隊首是誰,上一個打飯的是誰,佇列前\(b[i]\)個人的狀態
然後我們根據這些資訊設立DP狀態,記\(f[i][j][k]\)表示該第\(i\)個人打飯(等價於前\(i-1\)個人已經買完飯)此時佇列前7個人的狀態是\(j\),上一個打飯的人是\(i+k\)。由於打飯的人在\(i\)的前後都可以,所以\(k\)的範圍就是\([-8,8]\),加上偏移量就是\([0,16]\)
接下來我們考慮轉移,分為兩種情況:
- 第\(i\)個人已經買完飯了,也就是說直接將狀態轉移到\(i+1\)就可以了
f[i+1][j>>1][k-1]=min(f[i+1][j>>1][k-1],f[i][j][k]);
- 第\(i\)個人還沒有買飯,那就在所有人都能忍受的範圍內列舉買飯的人是誰
f[i][j|(1<<l)][l]=min(f[i][j|(1<<l)][l],f[i][j][k]+(t[i+k]^t[i+l]));
狀態初始化為\(f[1][0][7]\)表示該第\(1\)個人買飯,此時身後所有人都沒有買完,上一個買的人是第\(7-8\)個,最後統計列舉\(i\)然後取\(f[n+1][0][i]\)的最小值
程式碼:
#include<bits/stdc++.h> using namespace std; namespace zzc { const int maxn = 1005; int f[maxn][256][20],b[maxn],t[maxn]; int T,n; void work() { scanf("%d",&T); while(T--) { memset(f,0x3f,sizeof(f)); scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d%d",&t[i],&b[i]); f[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(f[i][j][k+8]!=0x3f3f3f) { 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 lim=0x3f3f3f; for(int l=0;l<=7;l++) { if(!((j>>l)&1)) { if(i+l>lim) break; lim=min(lim,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=0x3f3f3f; for(int i=0;i<=8;i++) ans=min(ans,f[n+1][0][i]); printf("%d\n",ans); } } } int main() { zzc::work(); return 0; }