1. 程式人生 > >Codeforces 8C 狀壓DP

Codeforces 8C 狀壓DP

順序 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