1. 程式人生 > >Hie with the Pie(poj3311)

Hie with the Pie(poj3311)

single const lang for tar 什麽是 def 由於 搜索

題目鏈接:http://poj.org/problem?id=3311

學習博客:https://blog.csdn.net/u013480600/article/details/19692985

Hie with the Pie
Time Limit: 2000MS Memory Limit: 65536K
Total Submissions: 9954 Accepted: 5368

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

Source

East Central North America 2006 題目大意:輸入n,接下來有(n+1)*(n+1)的矩陣,dist[i][j]代表i和j之間的距離,剛開始在0的位置,要求你1^n,每個點至少走一次,最後回到0,所需要的最短時間是多少 思路:

分析;本題和經典TSP旅行商問題類似,但是TSP要求每個節點僅走過1次,本題要求每個節點至少走過一次。

同樣的是本題也用狀態壓縮DP來解。令d[i][s]表示從0號點出發,走過集合S中的所有點(不包含起始0號點,但可以包含終止點0號點),且當前在i號點時所需的最小距離。

則:d[i][s]= min{ d[ j ][ s –{i}]+min_dist[j][i] } 其中min_dist[j][i]是指從j節點到i節點的最小距離, s表示節點集合,且包含i和j。

初值:d[ 0 ][ {} ] = 0,我們所求最小距離為:d[0][{0,1,2,3,…n}]

接下來我們來論證一下該題的正確性:首先 :d[0][{0,1,2,3,…n}]表示最終走過所有節點至少1次回到0號點的最小距離。則當走最後一步從i到0時,則必然d[0][{0,1,2,3,…n}] 是從所有的 d[i][{1,2,3,…n}] + min_dist[i][0]中選一個最小的值,1<=i<=n。

d[i][{1,2,3,…n}]也必然是從所有的d[j][{1,2,3…n}-{i}] + min_dist[j][i]中選一個最小的值,以此類推。所以行走最短路線的過程只能是我們上面的分析過程,不可能有其他方式,如果存在最短路徑我們必然可以通過以上解法求出來。

由於本題要求的是min_dist[i][j]即兩點間的最短距離,且不在乎重復走過一些點,所以需要先用floyd算法先求出任意兩點間的最小距離。

有個疑問,假設d[2][{1,2}]= 10,且dist[2][3]=10(2到3的真實距離為10)假設min_dist[2][3]=5,因為2到1再到3的距離為5,所以我們會求得d[3][{1,2,3}]=15嗎?此時我們走了兩次1號節點,有沒有可能d[3][{1,2,3}]的值更小一點,而我們卻漏了這個解?不可能的,我們當前走的路線是0->1->2->1->3得到的15,如果0->2->1->3得到的值小於15那麽d[3][{1,2,3}]的更小值肯定能從d[1][{1,2}]得到更新。所以這個遞推方程是不會丟失最優解的。

看代碼:
#include<iostream>
#include<string.h>
#include<map>
#include<cstdio>
#include<cstring>
#include<stdio.h>
#include<cmath>
#include<ctype.h>
#include<math.h>
#include<algorithm>
#include<set>
#include<queue>
typedef long long ll;
using namespace std;
const ll mod=1e9;
const int maxn=10+5;
const int maxm=1<<maxn;
const int maxx=1e4+10;
const ll maxe=1000+10;
#define INF 0x3f3f3f3f3f3f
#define Lson l,mid,rt<<1
#define Rson mid+1,r,rt<<1|1
int n;
int dist[maxn][maxn];//輸入的距離矩陣
int min_dist[maxn][maxn];//求出的最短路徑距離矩陣
int dp[maxn][maxm];//用於表示dp狀態,dp[i][{}]表示此時在i點,走過的點構成的集合{},集合用二進制存儲
bool vis[15][maxm];
void floyed()
{
    for(int i=0;i<=n;i++)
    {
        for(int j=0;j<=n;j++)
        {
            for(int k=0;k<=n;k++)
            {
                min_dist[j][k]=min(min_dist[j][k],min_dist[j][i]+min_dist[i][k]);
            }
        }
    }
}
int solve(int i,int s)//記憶化搜索,這個函數不會計算solve(0,0)
{
    if(vis[i][s]) return dp[i][s];//已經知道最短距離了
    vis[i][s]=true;//下面是尋找的過程,跟dfs有點相像
    int &ans=dp[i][s];//這裏用了一個引用,改變ans的同時也改變了dp[i][s]
    ans=mod;//賦值為無窮大
    for(int j=0;j<=n;j++)//集合s中的一位j
    {
        if(s&(1<<j)&&j!=i)//j==i代表在同一點  所以要!=
        {
            ans=min(ans,solve(j,s^(1<<i))+min_dist[j][i]);//為什麽是solve(j,s^(1<<i))  ,因為j變為起點了,同時要去掉i這一位,^運算相同為0,相異為1
        }
    }
    return ans;
}
int main()
{
    while(scanf("%d",&n)!=EOF)
    {
        if(n==0) break;
        for(int i=0;i<=n;i++)
        {
            for(int j=0;j<=n;j++)
            {
                cin>>dist[i][j];
                min_dist[i][j]=dist[i][j];
            }
        }
        floyed();//求出兩點的最短距離
        //for(int i=1;i<=3;i++) cout<<min_dist[0][i]<<" ";
        //cout<<endl;
        /*
        for(int i=0;i<=3;i++)
        {
            for(int j=0;j<=3;j++)
                cout<<min_dist[i][j]<<" ";//不理解的話可以用這個輸出來理解一下,因為我們已經求出了任意兩點之間的最短距離了,所以這樣是不會漏掉最優解的
            cout<<endl;
        }
        */
        memset(vis,false,sizeof(vis));
        dp[0][0]=0;//dp[0][0]  在0點  集合為空
        vis[0][0]=true;//已經知道距離的為true
        for(int i=1;i<=n;i++)
        {
            vis[i][1<<i]=true;//已經知道距離的為true
            dp[i][1<<i]=min_dist[0][i];//在i點,集合裏只有本身,距離也就是最短路求出來的兩點間最短距離
        }
        cout<<solve(0,(1<<(n+1))-1)<<endl;//註意這裏為何是(1<<(n+1))-1呢,因為有n+1位(加上0那一位),你需要的狀態是n+1位都為1,(1<<(n+1))-1就可以得到了
    }
    return 0;
}

Hie with the Pie(poj3311)