【题解】 [NOI Online #1 入门组]魔法 最短路+矩阵快速幂+floyd+dp Luogu6189

Legend

Link \(\textrm{to Luogu}\)

C 国由 \(n\) 座城市与 \(m\) 条有向道路组成,城市与道路都从 \(1\) 开始编号,经过 \(i\) 号道路需要 \(t_i\) 的费用。

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

\(1 \le n \le 100,0 \le m \le \frac{n(n-1)}{2},1 \le t_i \le 10^9,0 \le k \le 10^6\)

Editorial

本题十分有意思,为出题人点赞!

首先我们求出

  • \(F[0]_{i,j}\) 表示 \(i \rightarrow j\) 使用 \(0\) 次魔法的最少花费。
  • \(F[1]_{i,j}\) 表示 \(i \rightarrow j\) 使用 \(1\) 次魔法的最少花费。

这个可以 Floyd 直接分层图。

我们考虑从使用一次魔法推到使用两次魔法:\(F[2]_{i,j}=\min\limits_{k=1}^{n}F[1]_{i,k}+F[1]_{k,j}\)

从两次魔法推到三次 \(F[3]_{i,j}= \min\limits_{k=1}^{n} F[2]_{i,k}+F[1]_{k,j}\)

显然可以直接用矩阵优化这个转移。初始矩阵 \(F[0]\),转移矩阵 \(F[1]\)

复杂度 \(O(n^3\log k)\)

Off-topic

说句题外话,我在写这个题的分层图的时候,最开始一不小心没有建第二层(使用了魔法后)的图。

我写的是右乘转移,于是就过不了样例。改成了左乘就可以,并且可以 AC(官方数据也可以)。

这让我感到非常奇怪,我得到的转移矩阵明明都是错的,为什么还能 AC?

其实根据矩阵的结合律从右往左看,就容易发现这左乘是对的了。

Code

#include <bits/stdc++.h>

#define debug(...) fprintf(stderr ,__VA_ARGS__)
#define LL long long

using namespace std;

const int MX = 200 + 2;

int sz ,n ,m ,K;

void chkmin(LL &a ,LL b){a = std::min(a ,b);}
struct Matrix{
	LL A[102][102];
	Matrix operator *(const Matrix& B)const{
		Matrix C;
		for(int i = 1 ; i <= n ; ++i){
			for(int j = 1 ; j <= n ; ++j){
				C.A[i][j] = 1LL << 50;
				for(int k = 1 ; k <= n ; ++k){
					chkmin(C.A[i][j] ,A[i][k] + B.A[k][j]);
				//	chkmin(C.A[i][j] ,B.A[i][k] + A[k][j]);
				}
			}
		}
		return C;
	}
	void output(){
		for(int i = 1 ; i <= n ; ++i){
			for(int j = 1 ; j <= n ; ++j){
				debug("%lld%c" ,A[i][j] ," \n"[j == n]);
			}
		}
		debug("=======================");
	}
}org ,tr;

LL dis[MX][MX];



int main(){
	cin >> n >> m >> K;
	memset(dis ,0x3f ,sizeof dis);
	for(int i = 1 ; i <= 2 * n ; ++i) dis[i][i] = 0;
	for(int i = 1 ,u ,v ,w ; i <= m ; ++i){
		cin >> u >> v >> w;
		chkmin(dis[u][v] ,w);
		chkmin(dis[u][v + n] ,-w);
		chkmin(dis[u + n][v + n] ,w);
	}
	for(int k = 1 ; k <= 2 * n ; ++k)
		for(int i = 1 ; i <= 2 * n ; ++i)
			for(int j = 1 ; j <= 2 * n ; ++j)
				chkmin(dis[i][j] ,dis[i][k] + dis[k][j]);
	for(int i = 1 ; i <= n ; ++i){
		for(int j = 1 ; j <= n ; ++j){
//			debug("dis[%d][%d] = %lld ,mag = %lld.\n" ,i ,j ,dis[i][j] ,dis[i][j + n]);
			tr.A[i][j] = std::min(dis[i][j] ,dis[i][j + n]);
			org.A[i][j] = dis[i][j];
		}
	}
	
	// tr.output();
	while(K){
		if(K & 1) org = org * tr;
		tr = tr * tr ,K >>= 1;
	}
	printf("%lld\n" ,org.A[1][n]);

	return 0;
}
posted @ 2020-10-20 18:39  Imakf  阅读(145)  评论(0编辑  收藏  举报