# TIOJ 1014. 打地鼠 ###### tags: `Ans` ### 轉移式 * `dp[s][i]` * s 代表地鼠的狀態, 如果第 x 位是 1 代表第x支還活着 * i 代表最後是敲下哪一個位置的地鼠(結尾) * `dp[s][i] = min{ 0<=k<n | dp[在敲i之前][k結尾]+行走時間+一段時間到t[i]的倍數 }` * 因爲地鼠是有週期性的, 所以值還要在加上一段時間變成週期的倍數 * $dp(s,i) = min\{r\ |\ 0\leq k<n,\ r=([(dp(s\&~(1<<i),k)+abs(i-k)]-1)/t[i]+1)*t[i]\}$ * $[(dp(s\&~(1<<i),k)+abs(i-k)]$ * 在敲 i 之前, 最後一個敲 k 的時間 + 從 k 跑到 i 的時間 * $((x-1)/t[i]+1)*t[i]$ * 把 x 變成 t[i] 的倍數 m, m>=x * (x/t[i]+1)*t[i] 後小數點會被去掉加 1 再把 t[i] 乘回去, 能把他轉成 t[i] 的倍數 * 考慮 x 爲 t[i] 倍數的情況, 最後答案會太大 * 先把 x 減 1 ### 實作 採用 bottom-up, 考慮要如何枚舉狀態 觀察到 `s~(1<<k)` 的部分, 一定小於等於 s 把 s 從小開始枚舉 當決定了 s, 該決定 i 注意 i 的意思是 s 的最後一個敲的地方, 所以必須保證 `s&(1<<i)` ### 邊界條件, 初始化 因爲 dp 要取 min, 所以一開始全部設成 INF * 邊界設定有兩種方法 ```cpp for (int i=0; i<n; i++) dp[1<<i][i] = t[i]; ``` ```cpp dp[0][0] = 1; // 一開始站在第0個左邊, 要花 1 走第 0 // 決定了這個狀態後 s 從 1 開始枚舉 // 可以發現第一種邊界設定的狀態都可以繼續轉移來這裏 ``` ### CODE ```cpp #include <iostream> #include <cstring> using namespace std; const int N = 16; int dp[N][1<<N], t[N], n; int main() { cin >> n; for (int i=0; i<n; i++) cin >> t[i]; memset(dp,0x7f,sizeof(dp)); dp[0][0] = 1; for (int s=1; s<(1<<n); s++) for (int i=0; i<n; i++) if (s&(1<<i)) for (int k=0,x; k<n; k++) x = dp[k][s&~(1<<i)] + abs(k-i), dp[i][s] = min( dp[i][s],((x-1)/t[i]+1)*t[i]); int res = 0x7f7f7f7f; for (int i=0; i<n; i++) res = min( res, dp[i][(1<<n)-1]); cout << res << '\n'; } ```