Codeforces Beta Round 8 C. Looking for Order 【状压DP】

C. Looking for Order

Codeforces Beta Round 8 C. Looking for Order 【状压DP】_第1张图片

题意

平面直角坐标系上有 n n n 个物品,还有一个初始背包位置 ( x 0 , y 0 ) (x_0, y_0) (x0,y0),从背包位置出发,每次最多携带两个物品回来背包,求把所有物品带回背包位置要走的最短距离,并给出详细方案

思路

Codeforces Beta Round 8 C. Looking for Order 【状压DP】_第2张图片

看上面这张图,如果我们从 0 0 0 号点出发,每次只访问一个点的话,这样子访问两个点的总距离是: 2 a + 2 b 2a + 2b 2a+2b,但是如果我们一次访问两个点再回去背包:

Codeforces Beta Round 8 C. Looking for Order 【状压DP】_第3张图片

这样子的总距离是: 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
#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;
}

你可能感兴趣的:(动态规划,算法,c++)