题目描述
Lena喜欢秩序井然的生活。一天,她要去上大学了。突然,她发现整个房间乱糟糟的——她的手提包里的物品都散落在了地上。她想把所有的物品都放回她的手提包。但是,这里有一点问题:她一次最多只能拿两个物品,她也不能移动她的手提包。并且,因为她爱整洁的习惯,如果她拿起了一个物品,她也不能将它放在其他地方,除非放回她的手提包。
Lena把她的房间划分为了一个平面直角坐标系。现在Lena给你她的手提包和每个散落的物品的坐标(当然,一开始的时候她就和手提包站在一个地方)。她从坐标 $(x1,y1)$ 走到坐标 $(x2,y2)$ 需要用 $(x1-x2)^{2}+(y1-y2)^{2}$ 单位的时间。现在,Lena将告诉你她的房间的情况,请你为Lena找到一个拾起每个物品的顺序,使她拾起所有物品所需的总时间最小。当然,Lena最后需要返回她的手提包。
输入输出格式
输入格式
输入文件的第一行为Lena的手提包的坐标 $x_{s}$ , $y_{s}$ 。第二行为一个正整数 n ,表示总的需要拾起的物品数。接下来的 n 行每行包括两个整数,表示每个物品的坐标。
输出格式
输出的第一行为一个正整数,表示Lena拾起所有物品所需的最小时间。
输出的第二行为Lena拾起每个物品的顺序。(每一个物品由它的编号代表,0表示手提包)她应该从手提包(0)出发,在手提包(0)结束。
如,0 1 2 0 3 0
表示她从手提包出发,先拾起1号物品,再拾起2号物品,然后返回手提包(并放下1和2),再拾起3号物品,最后返回手提包。
如果有多条允许的路径,输出任一条。
输入输出样例
输入样例#1:
输出样例#1:
输入样例#2:
输出样例#2:
思路
状压DP
以当前已经取了哪些物品作为状态。
$n<=24$ 的数据范围和512MB的空间限制基本上就标志着这道题是一个标准的状压。
为了节省空间,我们就用 $1<<i-1$ 表示第 i 个物品有无被取过。由于一次可以选择拿一个或者两个物品,考虑在状态转移的时候枚举这次拿的两个物品(如果两个物品相同就处理为这次只拿一个)。
由于要输出拿的方法,我们就再用一个数组记录当前状态是从之前的哪个状态转移过来的即可。
需要注意的是,可以证明,若设每次离开手提包捡拾物品为一次,则各次之间的顺序不影响最终答案。
如,第一次拿物品1,第二次拿物品2,3与第一次拿物品2,3,第二次拿物品1的所用时间是一样的。这样,我们在转移的时候就从编号小的物品向编号大的物品找寻,只要找到了当前的一种可用方案就可以进入下一个状态了,这样可以节省时间(同时避免重复计算)。
代码
#include<cmath> #include<cstdio> #include<string> #include<cstring> #include<iostream> #include<algorithm> #define re register int using namespace std; const int MAXN=24, INF=2e9; struct obj { int x, y; } things[MAXN+1]; int n; int dp[1<<MAXN|1], pre[1<<MAXN|1]; int dis[MAXN+1][MAXN+1]; inline int min( int a, int b ) { return a<b?a:b; } inline int read(){ int x=0,w=1; char ch=getchar(); while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar(); if(ch=='-') w=-1,ch=getchar(); while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+ch-48,ch=getchar(); return x*w; } inline void solve(); int main( ) { for(re i=0;i<=(1<<MAXN);++i) dp[i]=INF,pre[i]=0; things[0].x=read(); things[0].y=read(); n=read(); for(re i=1;i<=n;++i) things[i].x=read(),things[i].y=read(); for(re i=0;i<=n;++i) for(re j=0;j<=n;++j) { dis[i][j]=dis[j][i]= (things[i].x-things[j].x)*(things[i].x-things[j].x)+ (things[i].y-things[j].y)*(things[i].y-things[j].y); } solve(); printf("%d ",dp[(1<<n)-1]); int now=(1<<n)-1; //从全部拿完的状态开始往前推 while (now!=0) { printf("0 "); int update=now^pre[now]; for(int i=1;i<=n;i++) if(update&1<<i-1) printf("%d ",i); now=pre[now]; } printf("0 "); return 0; } inline void solve( ) { dp[0]=0,pre[0]=0; for(int m=0;m<(1<<n);m++) { if(dp[m]==INF) continue; //如果当前状态没有被更新过直接continue for(int i=1;i<=n;i++) { if(m&1<<i-1) continue; //如果已经拿过了 for(int j=1;j<=n;j++) { if(m&1<<j-1) continue; if(dp[m|(1<<i-1)|(1<<j-1)]>dp[m]+dis[0][i]+dis[i][j]+dis[j][0]) { dp[m|(1<<i-1)|(1<<j-1)]=dp[m]+dis[0][i]+dis[i][j]+dis[j][0]; pre[m|(1<<i-1)|(1<<j-1)]=m; //状态转移 } } break; } } return; }
转自星烁晶熠辉