zoukankan      html  css  js  c++  java
  • 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;
    }
    
  • 相关阅读:
    Linux基础命令—umask
    dd(处理文件)
    HDU6446 Tree and Permutation(树、推公式)
    UVA5913 Dictionary Sizes(字典树)(转载)
    Luogu P1330 封锁阳光大学 (黑白染色)
    codeforces 1025B Weakened Common Divisor(质因数分解)
    UVA1623 Enter The Dragon (贪心)
    HDU6395 Sequence(矩阵快速幂+数论分块)
    BZOJ1257 [CQOI2007]余数之和 (数论分块)
    Codeforces 1011C Fly(二分+模拟)
  • 原文地址:https://www.cnblogs.com/suxxsfe/p/12896692.html
Copyright © 2011-2022 走看看