给出n个点(n<=15),从(0,0)出发,问最短走多远,才能经过所有的点,可以在任何位置结束。
今天有学弟来问的一个题,之前做的时候还没学状压,写了个爆搜+剪枝,数据太水$O(n!)$给水过去了,今天再看发现算是个状压dp的入门题吧,之前的代码提交发现数据加强TLE了,就又补了一下。
对经过的点状态进行状压,状态表示为i,位运算上每位为0表示未经过,为1则表示经过,采用dp[i][j]表示i状态下,若当前在j点的最短距离,对于已经经过的点k,那么可以从k走到j点,就有$dp[i][j]=dp[i-(1<<j)][k]+dis(j,k)$,转移方程也就出来了,$dp[i][j]=min(dp[i][j],dp[i-(1<<j)][k]+dis(k,j)$,其中满足$ (1<<k)&i $不为0,转移也就很方便了,最后对满状态下的(1<<n)-1,枚举最后一项取最小的即可,时间复杂度$O(n*2^{n})$。
最后附上代码
#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef long double ld; typedef unsigned long long ull; typedef pair <double,double> pdd; #define rep(i,x,y) for(int i=x;i<y;i++) #define rept(i,x,y) for(int i=x;i<=y;i++) #define per(i,x,y) for(int i=x;i>=y;i--) #define all(x) x.begin(),x.end() #define pb push_back #define fi first #define se second #define mes(a,b) memset(a,b,sizeof a) #define mp make_pair #define dd(x) cout<<#x<<"="<<x<<" " #define de(x) cout<<#x<<"="<<x<<" " #define debug() cout<<"I love Miyamizu Mitsuha forever. " const double inf=1e18; pdd point[20]; double dp[35000][20]; double dis(const pdd &s1,const pdd &s2) { return sqrt( (s1.fi-s2.fi)*(s1.fi-s2.fi)+(s1.se-s2.se)*(s1.se-s2.se) ); } int main() { ios::sync_with_stdio(false); cin.tie(0); int n; cin>>n; rep(i,0,n) cin>>point[i].fi>>point[i].se; rep(i,1,1<<n) { rep(j,0,n) { dp[i][j]=inf; if((1<<j)&i) { if((1<<j)==i) { dp[i][j]=dis(mp(0,0),point[j]); continue; } else { rep(k,0,n) if(k!=j&&((1<<k)&i)) dp[i][j]=min(dp[i][j],dp[i-(1<<j)][k]+dis(point[k],point[j])); } } } } double ans=inf; rep(i,0,n) ans=min(ans,dp[(1<<n)-1][i]); cout<<fixed<<setprecision(2)<<ans<<" "; return 0; }