本题从众多郊区里面选取R个郊区,要求走过一遍之后的中距离最短,那么我们首先就是要得到R个郊区彼此之间的最短距离,然后根据这个最短距离再来安排怎么走合适。
那么要求彼此之间的最短距离问题就得用到数据结构里面学到的弗洛伊德算法了,根据弗洛伊德算法将所有的最短距离求出来。
那么问题就回归到了经典的TSP问题了,需要使用状压dp,建立一个状态转移数组:dp[i][j],i是用二进制表示的状态,j是当前要最那一个郊区下手。
那么状态转移方法为:dp[state|st][k+1] = min(dp[state|st][k+1], dp[state][j+1] + a[play[j+1]][play[k+1]]);
这个状态转移方程是源于以当前state的状态下去寻找一个没有被选定的郊区,然后将空位给填上。这样就可以。
有人可能对为什么直接从0循环到小于len就行产生异或,在这里我们可以断定既然遍历到某个状态了,那么随便选择一个已经选过的郊区作为起点都一定已经有结果了。这与二进制加法走过的顺序有关。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 200+10;
int n, m, r;
int dp[1<<15][20];
int play[20];
int a[maxn][maxn];
//求全部节点的最短路。
void floyed()
{
    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])
                {
                    a[i][j] = a[i][k] + a[k][j];
                }
            }
        }
    }
}

signed main()
{
    int x, y, val;
    memset(a, 0x3f, sizeof(a));
    memset(dp, 0x3f, sizeof(dp));
    cin>>n>>m>>r;
    for (int i=1;i<=r;i++) cin>>play[i];
    for (int i=1;i<=m;i++) {
        cin>>x>>y>>val;
        a[x][y] = val;
        a[y][x] = val;
    }
    floyed();
    //对于最开始走的那个点是没有花费的。
    for (int i=0;i<r;i++) 
    {
        dp[1<<i][i+1] = 0;
    }
    int len = (1<<r)-1;
    for (int state=1;state<len;state++)
    {
        //挑选一个起点,起点一定是已经走过的点。
        for (int j=0;j<r;j++)
        {
            if ((state>>j)&1)
            {
                //挑选一个终点,一定是还没有走过的点。
                for (int k=0;k<r;k++) 
                {
                    if (((state>>k)&1)==0)
                    {
                        int st = 1<<k;
                        dp[state|st][k+1] = min(dp[state|st][k+1], dp[state][j+1] + a[play[j+1]][play[k+1]]);
                    }
                }
            }
        }
    }
    int ans = 0x7f7f7f7f;
    for (int i=1;i<=r;i++)
    {
        ans = min(ans, dp[len][i]);
    }
    cout<<ans;
    return 0;
}