suxxsfe

一言(ヒトコト)

P6190 [NOI Online #1 入门组]魔法

https://www.luogu.com.cn/problem/P6190

矩阵优化的最短路
其实这题里的矩阵运算并不是乘法,由此说明在转移的时候并不一定要往矩阵乘法的原本形式上靠,只要符合结合律能快速“幂”就行

题目描述

C 国由 \(n\) 座城市与 \(m\)有向 道路组成,城市与道路都从 \(1\) 开始编号,经过 \(i\) 号道路需要 \(t_i\) 的费用。
现在你要从 \(1\) 号城市出发去 \(n\) 号城市,你可以施展最多 \(k\) 次魔法,使得通过下一条道路时,需要的费用变为原来的相反数,即费用从 \(t_i\) 变为 \(-t_i\)
请你算一算,你至少要花费多少费用才能完成这次旅程。

注意:使用魔法只是改变一次的花费,而不改变一条道路自身的 \(t_i\)
最终的费用可以为负,并且一个城市可以经过多次(包括 \(n\) 号城市)。

输入格式

输入的第一行有三个整数,分别代表城市数 \(n\),道路数 \(m\) 和魔法次数限制 \(k\)
\(2\) 到第 \((m+1)\) 行,每行三个整数。第 \((i+1)\) 行的整数 \(u_i,v_i,t_i\)

输出格式

输出一行一个整数表示最小的花费。


先写出dp的方程,\(f(p,i,j)\) 表示的是用了 \(p\) 次魔法,从 \(i\)\(j\) 的最短路长度

  • \(p=0\),直接 floyd
  • \(p=1\),枚举每个边 \(k\),则 \(f(1,i,j)=\min(f(1,i,j),f(0,i,u_k)+f(0,v_k,j)-t_k)\),就是在边 \(k\) 使用了魔法
  • \(p>1\),这时 \(k\) 表示的是枚举的点,\(f(x+1,i,j)=\min(f(x+1,i,j),f(x,i,k)+f(1,k,j))\)

然后前两种可以直接按式子跑出来,第三种要用矩阵加速
第三种里,这个取 \(\min\) 相当于一般矩阵乘法里的求和,然后 \(f(x,i,k)+f(1,k,j)\) 里的加号相当于矩阵乘法里的乘号
所以乘一个 \(f(1)\) 的矩阵,也就能从上一个矩阵转移到下一个矩阵了
然后 很容易猜出(其实证明不难),这个是有结合律的,可以快速幂,给 \(f(1)\) 矩阵做 \(k\) “次方”

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<map>
#include<iomanip>
#include<cstring>
#define reg register
#define EN std::puts("")
#define LL long long
inline int read(){
	register int x=0;register int y=1;
	register char c=std::getchar();
	while(c<'0'||c>'9'){if(c=='-') y=0;c=std::getchar();}
	while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
	return y?x:-x;
}
struct data{
	LL a[105][105];
}ans,a;
LL dis[106][106],t[2506];
int u[2506],v[2506];
int n,m;
inline void pre(int k_){
	for(reg int k=1;k<=n;k++)
		for(reg int i=1;i<=n;i++)
			for(reg int j=1;j<=n;j++)
				dis[i][j]=std::min(dis[i][j],dis[i][k]+dis[k][j]);
	if(!k_) std::printf("%lld",dis[1][n]),std::exit(0);
	for(reg int i=1;i<=n;i++)
		for(reg int j=1;j<=n;j++) a.a[i][j]=dis[i][j];
	for(reg int k=1;k<=m;k++)
		for(reg int i=1;i<=n;i++)
			for(reg int j=1;j<=n;j++)
				a.a[i][j]=std::min(a.a[i][j],dis[i][u[k]]+dis[v[k]][j]-t[k]);
	if(k_==1) std::printf("%lld",a.a[1][n]),std::exit(0);
	for(reg int i=1;i<=n;i++)
		for(reg int j=1;j<=n;j++) ans.a[i][j]=a.a[i][j];
}
inline data mul(data a,data b){
	data c;
	for(reg int i=1;i<=n;i++)
		for(reg int j=1;j<=n;j++) c.a[i][j]=1e18;
	for(reg int i=1;i<=n;i++)
		for(reg int j=1;j<=n;j++)
			for(reg int k=1;k<=n;k++)
				c.a[i][j]=std::min(c.a[i][j],a.a[i][k]+b.a[k][j]);
	return c;
}
inline void power(int b){
	while(b){
		if(b&1) ans=mul(ans,a);
		b>>=1;a=mul(a,a);
	}
}
int main(){
	n=read();m=read();int k=read();
	std::memset(dis,0x3f,sizeof dis);std::memset(a.a,0x3f,sizeof a.a);
	for(reg int i=1;i<=n;i++) dis[i][i]=a.a[i][i]=0;
	for(reg int i=1;i<=m;i++){
		u[i]=read();v[i]=read();dis[u[i]][v[i]]=t[i]=read();
	}
	pre(k);
	power(k-1);
	std::printf("%lld",ans.a[1][n]);
	return 0;
}
posted @ 2020-05-15 19:19  suxxsfe  阅读(185)  评论(0编辑  收藏  举报