很早很早以前就以爲是一個麻煩的題目也感受很簡單不想寫……結果今天寫的時候發現以前的想法假了(因此不要口胡題目不寫代碼)c++
\(\text{Solution:}\)ide
看到 \(b[i]\leq 7\) 的數據範圍先想到狀壓。spa
考慮狀壓一我的後面的人的打飯狀況,那麼能夠設 \(f[i][S]\) 表示前 \(i\) 我的已經打完飯,\(i+1\to i+b[i]\) 的打飯狀況是 \(S\) 的最小時間。設計
可是會發現一件很棘手的事情:當咱們去用 \(S\) 轉移的時候,把一我的放到前面影響的不單單是 \(i\) 一個位置,更有 \(i\to pos\) 之間的全部位置。code
也就是說,若是我想要把一個位置提早,那麼它必須知足全部尚未打飯的人的容忍度,這個東西是單調遞減的。ci
因此咱們能夠考慮在 \(dp\) 的時候來維護一個範圍,若是當前枚舉的人已經超過合法範圍了,那咱們就直接 \(pass.\)get
繼續考慮,若是咱們讓一我的 \(j\) 到前面去,代價應該是什麼呢?it
因爲這個狀態的設計,咱們發現咱們沒有辦法得到上一次打飯的人的編號。因而,這個編號應該也做爲 \(dp\) 裏面的一個維度。io
發現空間很大怎麼辦,考慮到這我的和 \(i\) 的差異不超過 \(8\) 因而咱們能夠考慮用一個 「位移長度」 \(k\) ,以 \(i+k\) 表明上一次打飯的人的編號。這樣就解決了空間的問題。
繼續考慮,發現若是這樣設計狀態 對於第一個位置 \(1\) 來講,\(i\) 已經打完飯的最優解其實是不是很好肯定的。由於第一道菜不須要時間。因而咱們稍微改一下方程:
\(f[i][j][k]\) 表示前 \(i-1\) 我的全都打完飯了,當前人們的打飯狀態爲 \(j,\) 上一次打飯的人的編號是 \(i+k\) 的最小時間。
發現這個 \(k\) 其實是能夠到 \(-8\) 的,由於能夠把一個編號靠後的人拿到前面去,這樣上一次打飯的人和 \(i\) 這一層就有可能差出 \(8.\)
爲了解決這個問題,考慮把數組總體平移一下,加上一個 \(8\) 就好了。
那麼,考慮轉移:若是當前 \(i\) 已經打飯了,觀察一下這個狀態 顯然對於 \(f[i+1][j>>1][k+7]\) 這個狀態,\(f[i][j][k+8]\) 是和它等價的。
因而直接轉移便可。
另外一種轉移是考慮枚舉一我的去打飯,這個時候咱們須要同時維護一下一個容忍度的上界,轉移的時候還須要特別注意是否是第一我的。
關於初始化 \(f[1][0][7]=0\) 是指上一個打飯的人是 \(1\) 前面的一我的,且 \(0\) 後面的人都沒有打飯。
轉移就是:
這裏是異或的緣由是\(\text{a or b-a and b=a xor b}\)
最後統計答案要統計 \(n+1\) 的,由於狀態設計的是 \(i-1\) 所有填滿。
#include<bits/stdc++.h> using namespace std; const int dyx=0x3f3f3f3f; const int MAXN=2e3+10; int f[MAXN][1<<9][20]; int n,t[MAXN],b[MAXN]; inline int Min(int x,int y){return x<y?x:y;} void solve(){ scanf("%d",&n); for(int i=1;i<=n;++i)scanf("%d%d",&t[i],&b[i]); memset(f,dyx,sizeof f);f[1][0][7]=0; for(int i=1;i<=n+1;++i) for(int j=0;j<(1<<8);++j) for(int k=-8;k<8;++k){ if(f[i][j][k+8]!=dyx){ if(j&1)f[i+1][j>>1][k+7]=Min(f[i+1][j>>1][i+7],f[i][j][k+8]); else{ int R=dyx; for(int p=0;p<8;++p){ if(j>>p&1)continue; if(i+p>R)break; R=Min(R,i+p+b[i+p]); f[i][j|(1<<p)][p+8]=Min(f[i][j|(1<<p)][p+8],f[i][j][k+8]+(k+i>0?(t[i+k]^t[i+p]):0)); } } } } int ans=dyx; for(int i=0;i<=15;++i)ans=Min(ans,f[n+1][0][i]); cout<<ans<<endl; for(int i=1;i<=n;++i)t[i]=b[i]=0; } int main(){ int T; cin>>T; while(T--){ solve(); } return 0; }