【洛谷P6190】[NOI Online 入门组] 魔法

前言

提高组心态爆炸,来入门组划划水。
话说这题为什么能评到紫啊

题目

题目链接: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;
}
原文地址:https://www.cnblogs.com/stoorz/p/12447345.html