Codeforces 8C 狀壓DP
阿新 • • 發佈:2019-05-01
順序 break 枚舉 con set pri esp 狀壓dp clu
題意:有個人想收拾行李,而n個物品散落在房間的各個角落裏(n < 24)。現在給你旅行箱的坐標(人初始在旅行箱處),以及n個物品的坐標,你一次只能拿最多兩個物品,並且拿了物品就必須放回旅行箱,不能暫時放在地上。問最小的花費是多少?花費是笛卡爾距離的平方。
思路一看n 只有24,應該很容易想到要用狀壓DP. 那麽dp[i]表示i狀態並且回到原點的最小花費。那麽就暴力枚舉拿1個或兩個物品放回原點,然後轉移就行了。需註意,這個題目中拿物品的順序對答案無影響,比如先拿1號,再拿2號和先拿2號,再拿1號的花費是一樣的。然後滿懷欣喜的交了一發,TLE了。。。。我們對題目的性質發掘的不夠,既然枚舉的時候拿物品的順序對答案沒影響,那麽我們狀態轉移的時候順序改變對答案沒影響。比如現在有1, 2, 3, 4這4個物品沒有選,那麽先選1, 2再選3,4和先選3,4再選1, 2答案是一樣的。所以我們枚舉的時候,找到與之前沒選過的最靠前的一個,再找出與這個對應的其它狀態去更新就可以了。
代碼:
#include <bits/stdc++.h> #define pii pair<int, int> using namespace std; const int maxn = 30; int dis(pii x, pii y) { return (x.first - y.first) * (x.first - y.first) + (x.second - y.second) * (x.second - y.second); } int dp[1 << 24], pre[1 << 24]; pii s, a[maxn]; int n; int cost[30][30]; void print(int now) { if(now == 0) { printf("0 "); return; } print(now ^ pre[now]); for (int i = 0; i < n; i++) { if((pre[now] >> i) & 1) printf("%d ", i + 1); } printf("0 "); } int main() { scanf("%d%d",&s.first, &s.second); scanf("%d", &n); for (int i = 0; i < n; i++) { scanf("%d%d", &a[i].first, &a[i].second); } for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) { // int now = (1 << i) | (1 << j); // re.push_back(now); int tmp1 = dis(s, a[i]); if(i != j) { tmp1 += dis(a[i], a[j]); tmp1 += dis(a[j], s); } else { tmp1 += dis(a[i], s); } //dist.push_back(tmp1); cost[i][j] = tmp1; } memset(dp, 0x3f, sizeof(dp)); dp[0] = 0; for (int i = 0; i < (1 << n); i++) { for (int j = 0; j < n; j++) { if((i >> j) & 1) continue; for (int k = j; k < n; k++){ if((i >> k) & 1) continue; int now = (1 << j) | (1 << k); if(dp[i | now] > dp[i] + cost[j][k]) { dp[i | now] = min(dp[i | now], dp[i] + cost[j][k]); pre[i | now] = now; } } break; } } printf("%d\n", dp[(1 << n) - 1]); print((1 << n) - 1); }
Codeforces 8C 狀壓DP