题解 CF793D 【Presents in Bankopolis】

· · 题解

题意:

一个有向图,节点有编号,找一条经过 k 个点的最短路径,要求是当前边不能跃过已经经过的节点。即对于路径中的边 x \to y ,不存在以前经过的点 t 使得 x <t<y

--- 每一次经过一条边都会使得可以到达的边的范围缩小。 考虑到 $ 1 \leq n,k \leq 80 $ 我们不难想到直接把可以去的区间存下来做$\rm DP$, 再按照类似区间$\rm DP$的做法开一维 $0,1$ 表示当前到达的端点是左端点还是右端点,然后直接转移即可。 具体的说,我们可以设 $f[i][j][k][0/1]$ 表示经过 $i$ 个点,可以去的区间是 $[j,k]$ ,现在在 左端点/右端点。其中第一维可以用滚动数组存。 --- 剩下的一些小细节可以看代码: ```cpp const int N=85; int n,K,m,u,v,c,cc[N][N],f[2][N][N][2],i,j,k,nw,ans=1<<30; int main(){ memset(cc,1,sizeof cc); scanf("%d%d%d",&n,&K,&m); while(m--)scanf("%d%d%d",&u,&v,&c),cc[u][v]=min(cc[u][v],c); memset(f[0],1,sizeof f[0]); for(i=1;i<=n;++i)f[0][0][i][1]=f[0][i][n+1][0]=0; while(--K){ nw^=1;memset(f[nw],1,sizeof f[nw]); for(i=0;i<=n-1;++i)for(j=i+2;j<=n+1;++j)for(k=i+1;k<j;++k){ f[nw][i][k][1]=min(f[nw][i][k][1],min(f[!nw][i][j][1]+cc[j][k],f[!nw][i][j][0]+cc[i][k])); f[nw][k][j][0]=min(f[nw][k][j][0],min(f[!nw][i][j][0]+cc[i][k],f[!nw][i][j][1]+cc[j][k])); } } for(i=0;i<=n+1;++i)for(j=0;j<=n+1;++j)ans=min(ans,min(f[nw][i][j][0],f[nw][i][j][1])); if(ans>1<<20)ans=-1; printf("%d\n",ans); } ```