C. Looking for Order
题意
平面直角坐标系上有 n n n 个物品,还有一个初始背包位置 ( x 0 , y 0 ) (x_0, y_0) (x0,y0),从背包位置出发,每次最多携带两个物品回来背包,求把所有物品带回背包位置要走的最短距离,并给出详细方案
思路
看上面这张图,如果我们从 0 0 0 号点出发,每次只访问一个点的话,这样子访问两个点的总距离是: 2 a + 2 b 2a + 2b 2a+2b,但是如果我们一次访问两个点再回去背包:
这样子的总距离是: a + b + c a + b + c a+b+c,与前一种情况相比,少了 ( a + b ) (a + b) (a+b),多了 c c c 的距离,但是由于三角形两边之和大于第三边: a + b > c a + b > c a+b>c,所以 2 a + 2 b > a + b + c 2a + 2b > a + b + c 2a+2b>a+b+c,也就是第二种情况一定更优。
观察到这一个关键点后,我们可以给出结论:一定是两两访问,如果 n n n 是奇数,剩下的某个点单独访问。并且两两访问的先后顺序是无关的,可以先访问也可以后访问,彼此之间不影响最终答案。问题是:谁和谁组合在一起被访问。
这里我们考虑状压 D P DP DP,定义 d p [ S ] [ i ] dp[S][i] dp[S][i] 为访问状态为 S S S 且最后一个访问的点是 i i i 的最短距离。那么转移我们只需要枚举 S S S 中为 0 0 0 的那些位,枚举一个或两个。由于这道题的访问先后顺序是无关的,那么对于当前 S S S 为 0 0 0 的那些位,最终一定要被访问的,那么我们先考虑最低位的那个没被访问的点也无妨,假设为 i i i,看看 i i i 是和某个点组合在一起访问更好,还是它自己单独被访问更好,我们可以通过枚举 j j j 从 i → i \rightarrow i→ 更高位为 0 0 0 的那些位来实现这个转移过程
#include<bits/stdc++.h>
#define fore(i,l,r) for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n'
#define ull unsigned long long
#define ALL(v) v.begin(), v.end()
#define Debug(x, ed) std::cerr << #x << " = " << x << ed;
#define lowbit(x) ((x) & -(x))const int INF=0x3f3f3f3f;
const long long INFLL=1e18;typedef long long ll;const int N = 24;
int dp[1 << N];
std::pair<int,int> p[N + 1];
int path[1 << N];int dis(int i, int j){int d1 = std::abs(p[i].fi - p[j].fi);int d2 = std::abs(p[i].se - p[j].se);return d1 * d1 + d2 * d2;
}int main(){std::ios::sync_with_stdio(false);std::cin.tie(nullptr);std::cout.tie(nullptr);std::cin >> p[0].fi >> p[0].se;int n;std::cin >> n;fore(i, 1, n + 1) std::cin >> p[i].fi >> p[i].se;memset(dp, INF, sizeof(dp));dp[0] = 0;fore(S, 0, 1 << n){if(dp[S] == INF) continue;fore(i, 0, n)if(!(S >> i & 1)){fore(j, i, n)if(!(S >> j & 1))if(dp[S] + dis(0, i + 1) + dis(i + 1,j + 1) + dis(j + 1, 0) < dp[S | 1 << i | 1 << j]){dp[S | 1 << i | 1 << j] = dp[S] + dis(0, i + 1) + dis(i + 1 ,j + 1) + dis(j + 1, 0);path[S | 1 << i | 1 << j] = S; //path记录是从哪个状态转移过去的}break;}}std::cout << dp[(1 << n) - 1] << endl;int S = (1 << n) - 1;std::cout << 0 << ' ';while(S){int mv = S ^ path[S]; //得到不同的那些位置,就是组合在一起被访问的点int i = std::__lg(lowbit(mv)) + 1;std::cout << i << ' ';mv -= lowbit(mv);if(mv){i = std::__lg(lowbit(mv)) + 1;std::cout << i << ' ';}std::cout << "0 ";S = path[S];}return 0;
}