题目大意

从点1出发,你要按顺序完成k个任务,每个任务有要求的起点终点。
途中你可以在所在的位置建立一个传送门,而同时只能用两个传送门存在,如果超过两个,则必须(远程)关闭任意一个传送门。

解题思路

首先可以想到,所谓的k个任务有起点终点,就是按顺序走过2k个点,a->b,c->d这样的两个任务,可以转化为走过1->a->b->c->d这条路径。
先想到一种暴力操作,每次求出dp[i][x][a][b],表示已经完成i个任务,当前在x点,传送门分别在a,b。

如果我们要使用传送门来少走点路,肯定是走到一个接近x的传送门,在传送到接近终点的传送门。
但是直接在x建立一个传送门岂不是更偷懒,所以我们不必记录两个传送门,只需记录远的一个即可。这样就可以简化为dp[i][x][b]。

脑袋一拍,现在所在的点x也不用记录,我们在输入时将节点排成一行(就是第一句的思想),用数组a记录每个点;所以又可以简化为dp[i][b](当前在a[i])。
每次任务都枚举上一次任务留下的传送门位置,和这次传送门放在哪里来状态更新。

可以简化得到三种状态转移:(有点复杂)

  • 直接从a[i]走到a[i+1];
  • 枚举走到a[i+1]后传送门的位置,设它的位置为t。
    从a[i]走到t,将传送门放在t,再从t传送到b(另一个传送门),走到a[i+1];
  • 从a[i]传送到b,在从b走到t,将传送门放在t,再从t走到啊a[i+1]。

按照上面的做法,直接先用Floyd的思路求最短路,再加dp即可。

AC代码

#include<bits/stdc++.h>
using namespace std;
long long g[310][310],dp[610][610],ans=1e16,z;
int a[610],n,m,k;
int main()
{
    int t=1,x,y,i,j;
    scanf("%d%d%d",&n,&m,&k);
    a[1]=1;
    for(i=1;i<=n;i++)
        for(j=1;j<=n;j++)
            if(i==j) g[i][j]=0;
            else g[i][j]=1e16;
    for(i=2;i<=n;i++) dp[1][i]=1e16;
    for(i=1;i<=m;i++)
    {
        scanf("%d%d%lld",&x,&y,&z);
        g[x][y]=g[y][x]=min(g[x][y],z);
    }
    for(i=1;i<=k;i++)
    {
        scanf("%d%d",&x,&y);
        a[++t]=x,a[++t]=y;
    }
    for(i=1;i<=n;i++)
        for(j=1;j<=n;j++)
            for(k=1;k<=n;k++)
                if(g[i][j]+g[i][k]<g[j][k])
                    g[j][k]=g[i][j]+g[i][k];
    for(i=2;i<=t;i++)
    {
        for(j=0;j<=n;j++) dp[i][j]=1e16;
        x=a[i-1],y=a[i];
        for(j=1;j<=n;j++)
        {
            dp[i][x]=min(dp[i][x],dp[i-1][j]+min(g[x][y],g[j][y]));
            dp[i][j]=min(dp[i][j],dp[i-1][j]+min(g[x][y],g[j][y]));
            for(k=1;k<=n;k++)
                dp[i][k]=min(dp[i][k],min(dp[i-1][j]+g[j][k]+g[k][y],dp[i-1][j]+g[x][k]+g[k][y]));
        }
    }
    for(i=1;i<=n;i++) ans=min(ans,dp[t][i]);
    printf("%lld\n",ans);
    return 0;
}