CodeForces 8 C.Looking for Order(状压DP)

Description

给出 n n 个物品的二维坐标以及起点坐标,每次要从起点出去拿一件或两件物品回到起点,两点之间距离定义为欧氏距离的平方,问把所有物品拿回起点所走最短距离以及对应最短距离下拿物品的顺序

Input

首先输入两个整数sx,sy表示起点坐标,之后输入一整数 n n 表示物品数量,最后n行每行两个整数 xi,yi x i , y i 表示第 i i 个物品的坐标(1n24,|sx|,|sy|,|xi|,|yi|100)

Output

把起点编号为 0 0 ,物品编号为1~ n n ,输出最短距离和对应的路径

Sample Input

0 0
2
1 1
-1 1

Sample Output

8
0 1 2 0

Solution

n件物品编号 0 0 ~n1,起点编号为 n n d(i,j)表示 i,j i , j 两点距离,用 n n 01表示每个物品的状态, 0 0 表示已经被拿了,1表示还没被拿,多次出去拿东西的次序无关,主要是每次出去拿的物品编号,故对于一个状态 S S ,我们默认去拿S的二进制表示中编号最小的 1 1 ,以dp[S]表示对于状态 S S 至少要走多少距离可以把这些物品拿到,假设第i位为 S S 中编号最小的1,如果只拿一件,那么有转移 dp[S]=min(dp[S],dp[S2i]+2d(i,n)) d p [ S ] = m i n ( d p [ S ] , d p [ S − 2 i ] + 2 ⋅ d ( i , n ) ) ,如果拿两件,那么找到 S S 二进制表示中另一个1所在位置 j j ,那么有转移dp[S]=min(dp[S],dp[S2i2j]+d(n,i)+d(i,j)+d(j,n)),最后 dp[2n1] d p [ 2 n − 1 ] 即为最短距离,拿物品的顺序在转移过程中记录一个状态 S S 是由pre[S]=T转移过来的,那么这次拿的物品即为 ST S − T 1 1 <script type="math/tex" id="MathJax-Element-38">1</script>对应的编号

Code

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<ctime>
using namespace std;
typedef long long ll;
typedef pair<int,int>P;
const int INF=0x3f3f3f3f,maxn=(1<<24)+5;
int n,xx,yy,x[25],y[25],dis[25][25],dp[maxn],pre[maxn];
int dfs(int S)
{
    if(dp[S]!=-1)return dp[S];
    dp[S]=INF;
    for(int i=0;i<n;i++)
        if((S>>i)&1)
        {
            int T=S^(1<<i);
            int temp=dfs(T);
            if(dp[S]>temp+2*dis[i][n])
            {
                dp[S]=temp+2*dis[i][n];
                pre[S]=T;
            }
            for(int j=i+1;j<n;j++)
                if((S>>j)&1)
                {
                    int R=T^(1<<j);
                    int temp=dfs(R);
                    if(dp[S]>temp+dis[n][i]+dis[i][j]+dis[j][n])
                    {
                        dp[S]=temp+dis[n][i]+dis[i][j]+dis[j][n];
                        pre[S]=R;
                    }
                }
            break;
        }
    return dp[S];
}
void output(int S)
{
    if(!S)return ;
    for(int i=0;i<n;i++)
        if((S^pre[S])&(1<<i))
        {
            printf("%d ",i+1);
        }
    printf("0 ");
    output(pre[S]);
}
int main()
{
    scanf("%d%d",&xx,&yy);
    scanf("%d",&n);
    for(int i=0;i<n;i++)scanf("%d%d",&x[i],&y[i]);
    x[n]=xx,y[n]=yy;
    for(int i=0;i<=n;i++)
        for(int j=0;j<=n;j++)
            dis[i][j]=(x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]);
    int N=1<<n;
    memset(dp,-1,sizeof(dp));
    dp[0]=0;
    printf("%d\n",dfs(N-1));
    printf("0 ");
    output(N-1);
    printf("\n");
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值