Time Limit: 2000MS | Memory Limit: 65536K | |
Total Submissions: 5052 | Accepted: 2690 |
Description
The Pizazz Pizzeria prides itself in delivering pizzas to its customers as fast as possible. Unfortunately, due to cutbacks, they can afford to hire only one driver to do the deliveries. He will wait for 1 or more (up to 10) orders to be processed before he starts any deliveries. Needless to say, he would like to take the shortest route in delivering these goodies and returning to the pizzeria, even if it means passing the same location(s) or the pizzeria more than once on the way. He has commissioned you to write a program to help him.
Input
Input will consist of multiple test cases. The first line will contain a single integer n indicating the number of orders to deliver, where 1 ≤ n ≤ 10. After this will be n + 1 lines each containing n + 1 integers indicating the times to travel between the pizzeria (numbered 0) and the n locations (numbers 1 to n). The jth value on the ith line indicates the time to go directly from location i to location j without visiting any other locations along the way. Note that there may be quicker ways to go from i to j via other locations, due to different speed limits, traffic lights, etc. Also, the time values may not be symmetric, i.e., the time to go directly from location i to j may not be the same as the time to go directly from location j to i. An input value of n = 0 will terminate input.
Output
For each test case, you should output a single number indicating the minimum time to deliver all of the pizzas and return to the pizzeria.
Sample Input
3 0 1 10 10 1 0 1 2 10 1 0 10 10 2 10 0 0
Sample Output
8
传送门:状压dp
这题很像TSP问题,解法里都用到了floyd来处理最短路,我主要解释一下为什么这么做是对的。
我们用dp[s][i]表示经过点集为S(不包括点0),到达点i的最小代价,算法里判断dp[s][i]是否为合法状态是通过S对应第i位是否为1,这里可能就有人会产生疑问,我们是用floyd处理得到最短路的,假设点0到点3的最短路中途经过了点1和点2,那么dp[100(二进制)][3]应该也算非法啊,正确的应该是dp[111(二进制)][3],因为从点0到点3的最短路必经过点1和点2,对于这个质疑,确实很有说服力。
但是我们换一个角度来思考问题,做这道题的最终目的是为了什么?不就是为了得到最小代价吗?那么中间过程就让它出现那些无意义的状态又何妨。
所以问题变为这么dp得到的最终结果为什么是对的?我们来考虑对于一个最优解,从点0出发,第一个到达的点必定是直接邻接于点0的(即最短路中间不包含其他点)。我们用反证法来证明,假设不是,那么我们将最优解的第一步换成那个更短的,最终代价变小,并且还多了一些点,同样也满足遍历了所有点的条件,是更优解了。
因此,假设错误,推出了结论的正确性。
那么,继续往前走,第二个点与第一个点一定是邻接的吗?我们说不是,但是只可能经过点0,但无论怎么说第二个点到第一个点必然是最短路,不然继续按照上面的更换,会得到一个更优解。
从上面分析来看,我们加强命题:对于最优解对应的路径,每个点都有第一次出现的时刻,我们将这些点取出,按照先后顺序排好,那么有前后两点之间的距离必然是最短路。比如0,2,1,3,那么0->2,2->1,1->3必然是最短路,这个命题的证明可以用数学归纳法,可以仿照前面的证明。
有了这个加强版命题,我们可以把原问题转换成只考虑成每个点第一次出现的情况,不去考虑中间多次出现的复杂情况,因此我们可以从dp里看到,我们必然会求得最优解,假如最优次序是0,2,1,3,由于我们已经说明0->2,2->1,1->3是最短路,并且2->1中间只可能经过0,1->3中间只可能经过0,2,又由于0->2中间不经过其他点,因此dp第一步(0->2)是正确的,接着从0->2推出2->1,dp是也是正确的,一直下去都是正确的,所以说dp是可以得到最优解的。所以无视中间一些无意义的状态吧,因为我们只关心能否求得最优解。
代码:
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int inf=0x3f3f3f3f;
int dist[12][12];
int dp[1050][12];
int main()
{
int n;
while(cin>>n,n){
for(int i=0;i<=n;i++)
for(int j=0;j<=n;j++)
cin>>dist[i][j];
for(int k=0;k<=n;k++)
for(int i=0;i<=n;i++)
for(int j=0;j<=n;j++)
dist[i][j]=min(dist[i][j],dist[i][k]+dist[k][j]);
for(int s=0;s<1<<n;s++){
for(int i=1;i<=n;i++){
if((s|(1<<i-1))!=s) continue; //不合法
if(s==1<<i-1) dp[s][i]=dist[0][i]; //从0直达i
else{
dp[s][i]=inf;
for(int j=1;j<=n;j++) //枚举上一个经过的点j
if(i!=j&&(s|(1<<j-1))==s)
dp[s][i]=min(dp[s][i],dp[s^(1<<i-1)][j]+dist[j][i]);
}
}
}
int ans=inf;
for(int i=1;i<=n;i++)
ans=min(ans,dp[(1<<n)-1][i]+dist[i][0]);
cout<<ans<<endl;
}
return 0;
}
基于上面那个增强版命题,我们可以发现我们只需关注每个点第一次出现的排列即可,因为对于每个排列答案是知道的,只要将相邻两点的最短路加起来就可以了。
好吧,于是基于一种全排列的算法诞生了。
传送门:全排列