前言
提高组心态爆炸,来入门组划划水。
话说这题为什么能评到紫啊
题目
题目链接:https://www.luogu.com.cn/problem/P6190
C 国由 \(n\) 座城市与 \(m\) 条有向道路组成,城市与道路都从 \(1\) 开始编号,经过 \(i\) 号道路需要 \(t_i\) 的费用。
现在你要从 \(1\) 号城市出发去 \(n\) 号城市,你可以施展最多 \(k\) 次魔法,使得通过下一条道路时,需要的费用变为原来的相反数,即费用从 \(t_i\) 变为 \(-t_i\)。请你算一算,你至少要花费多少费用才能完成这次旅程。
注意:使用魔法只是改变一次的花费,而不改变一条道路自身的 \(t_i\);最终的费用可以为负,并且一个城市可以经过多次(包括 \(n\) 号城市)。
思路
当时下午看别人截图发给我这道题,没截数据范围,心想那不是分层图最短路 sb 题么。
然后今天看到 \(k\leq 10^6\)。完蛋。
有一说一,其实也不难。
设 \(f[k][i][j]\) 表示从 \(i\) 到 \(j\) 使用不超过 \(k\) 次膜法的最短路,显然有
\[f[k][i][j]=min(f[\frac{k}{2}][i][l]+f[\frac{k}{2}][l][j])
\]
先用 Floyd 求出最短路,然后矩阵乘法跑 t-1 次即可。
挺显然的吧。。。不知道倍增可不可以做。
时间复杂度 \(O(n^3+n^2m+n^3\log k)\)。
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=110,M=2510;
int n,m,t;
ll dis[N][N];
struct edge
{
int u,v,dis;
}e[M];
struct matrix
{
ll a[N][N];
matrix()
{
memset(a,0x3f3f3f3f,sizeof(a));
}
friend matrix operator *(matrix a,matrix b)
{
matrix c;
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
for (int k=1;k<=n;k++)
c.a[i][j]=min(c.a[i][j],a.a[i][k]+b.a[k][j]);
return c;
}
}mat;
matrix fpow(matrix f,int k)
{
matrix ans;
memcpy(ans.a,f.a,sizeof(ans.a));
for (;k;k>>=1,f=f*f)
if (k&1) ans=ans*f;
return ans;
}
int main()
{
memset(dis,0x3f3f3f3f,sizeof(dis));
scanf("%d%d%d",&n,&m,&t);
for (int i=1,x,y,z;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&z);
dis[x][y]=z;
e[i].u=x; e[i].v=y; e[i].dis=z;
}
for (int i=1;i<=n;i++)
dis[i][i]=0;
for (int k=1;k<=n;k++)
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
if (i!=j && j!=k && i!=k)
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
if (!t) return !printf("%lld",dis[1][n]);
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
{
mat.a[i][j]=dis[i][j];
for (int k=1;k<=m;k++)
mat.a[i][j]=min(mat.a[i][j],dis[i][e[k].u]+dis[e[k].v][j]-e[k].dis);
}
matrix ans=fpow(mat,t-1);
printf("%lld",ans.a[1][n]);
return 0;
}