[POJ3311] [ECNA2006] Hie with the Pie [状态压缩][dp][floyd]

[ L i n k \frak{Link} Link] | 0 \frak{0} 0点出发,经过每个点至少一次,回到 0 \frak{0} 0点。求经过的最小距离。点数 1 ≤ n ≤ 10 \frak{1\le n\le 10} 1n10


当然不可能考虑整个回路怎么搞。
回路经常有一种方法,就是把 0 \frak{0} 0点拆出来,拆出一个起点和一个终点
最短回路就是从起点经过所有点到达终点。

“怎么做?”
先不要什么都没有就去考虑问题。记录一个状态, f ( s t a t e ) \frak{f(state)} f(state)表示从起点到达 s t a t e \frak{state} state经过的最短距离。
不过只有一维是不够的,还要记录下当前所在位置,所以应该是 f ( p o s i t i o n , s t a t e ) \frak{f(position,state)} f(position,state)
转移到下一个 p o s i t i o n ′ \frak{position'} position的时候就合并上 p o s i t i o n → p o s i t i o n ′ \frak{position\to position'} positionposition的最短路……?
这么讲意味着要合并最短路的距离和经过的点。可是真的要这样吗?
最短路经过的点不好合并进去。——如果不去考虑最短路经过的所有点呢?

那就要考虑最短路径经过的点是个什么情况了。
记为x。假如说在之前的状态里面,x点已经被访问过了,那当然不用管它;
如果x点没有被访问过?——那也不用去管。
既然x点没有被访问过,那么访问过x点的状态就在未来的状态里面。
所以说根本不用管,这就是重复状态而已,如果复杂度已经够了也不用想怎么优化掉它
如果非要去合并上最短路经过的所有点,可能还会更不好搞。

最终时间复杂度 Θ ( n 2 2 n ) \frak{\Theta(n^22^n)} Θ(n22n)
不过其实还有另外一种做法,注意到范围是10,我们又知道11以内都可以阶乘复杂度过1s,那可能可以全排列水过。


#Code
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<queue>
#include<ctime>
using namespace std;
int F[262150][20]={};
int A[20][20]={};
int n;
void out(int x)
{
	while(x)
	{
		cout<<(x&1);x>>=1;
	}
	cout<<" ";
}
int main()
{
	while(~scanf("%d",&n))
	{
		if(!n)return 0;
		n+=2;
		for(int i=1;i<n;++i)
		{
			for(int j=1;j<n;++j)
			{
				scanf("%d",&A[i][j]);
				if(A[i][j]<0)A[i][j]=0;
			}
		}
		for(int i=1;i<=n;++i)A[i][n]=A[i][1],A[n][i]=A[1][i];
		A[n][n]=0;
		for(int k=1;k<=n;++k)
		{
			for(int i=1;i<=n;++i)
			{
				for(int j=1;j<=n;++j)
				{
					if(A[i][j]<=A[i][k]+A[k][j])continue;
					A[i][j]=A[i][k]+A[k][j];
				}
			}
		}
		memset(F,0x3f,sizeof(F));
		F[1][1]=0;
		for(int i=1;i<=(1<<n)-1;++i)
		{
			for(int j=1;j<=n;++j)
			{
				if(F[i][j]==0x3f3f3f3f)continue;
				if(!(i&(1<<j-1)))continue;
				for(int v,k=1;k<=n;++k)
				{
					if(i&(1<<k-1))continue;
					v=i|(1<<k-1);
					F[v][k]=min(F[v][k],F[i][j]+A[j][k]);
				}
			}
		}
		printf("%d\n",F[(1<<n)-1][n]);
	}
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值