[NOI Online 2020 #1]魔法 题解

题意简述

给定一个 \(n\) 个点 \(m\) 条边的带权有向图,你可以进行至多 \(k\) 次操作,使得下一次通过路径的权值变为其相反数,之后再变回来。

问从 \(1\) 号点到 \(n\) 号点的最短路。

\(n≤100,m≤2500,k≤10^6\)

Solution

先考虑 \(70\) 分怎么做:

\(k=0\) 时直接跑个 \(floyd\) 就好了。

\(k=1\) 时也很简单,枚举对哪条边使用魔法。

\(f(k,i,j)\) 为进行至多 \(k\) 次操作,从 \(i\)\(j\) 的最短路,有方程 \(f(1,i,j)=min \left\{f(0,i,e_u)+f(0,e_v,j)-e_t\right\}\)

拓展到 \(k\) 较大时的情况,有 \(f(k,i,j)=min \left\{f(x,i,p)+f(k-x,p,j)\right\} \left(0<x<k\right)\)

直接转移是 \(O(n^3k)\) 的,显然无法通过。

考虑优化,发现这个转移方程的形式就非常得好,它是一个广义矩阵乘法,满足结合律,于是我们可以直接对其矩阵快速幂。

时间复杂度 \(O(n^3logk+ n^2m)\)

\(NOI2020\)\(D1T1\) 也有用到这个东西,还是蛮重要的。

记得特判 \(k=0\)

Code

#include<bits/stdc++.h>
#define IL inline
#define RE register
#define ll long long
#define N 110
#define M 2550
#define INF (1ll<<60)
IL int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')x=x*10+(ch^48),ch=getchar();
	return x*f;
}

int n,m,k;

template<class T1,class T2>IL void cmin(T1 &x,T2 y){x=x<y?x:y;}

struct Matrix
{
	ll a[N][N];
	Matrix(){memset(a,63,sizeof(a));}
	void copy(ll b[][N]){for(RE int i=1;i<=n;++i)for(RE int j=1;j<=n;++j)cmin(a[i][j],b[i][j]);}
	Matrix operator *(const Matrix &x)const
	{
		Matrix ret;
		for(RE int i=1;i<=n;++i)
			for(RE int j=1;j<=n;++j)
				for(RE int k=1;k<=n;++k)
					cmin(ret.a[i][j],a[i][k]+x.a[k][j]);
		return ret;
	}
}base,ans;

struct Edge{int u,v,t;}e[M];

ll dis[N][N];

IL void floyd()
{
	for(RE int k=1;k<=n;++k)
		for(RE int i=1;i<=n;++i)
			for(RE int j=1;j<=n;++j)
				cmin(dis[i][j],dis[i][k]+dis[k][j]);
}

int main()
{
	n=read(),m=read(),k=read();
	memset(dis,63,sizeof(dis));	
	for(RE int i=1;i<=n;++i)dis[i][i]=0;
	for(RE int i=1;i<=m;++i)
	{
		e[i]=(Edge){read(),read(),read()};
		dis[e[i].u][e[i].v]=e[i].t;
	}
	floyd();	
	if(!k)return printf("%lld",dis[1][n]),0;
	for(RE int i=1;i<=m;++i)
		for(RE int x=1;x<=n;++x)
			for(RE int y=1;y<=n;++y)
				cmin(base.a[x][y],dis[x][e[i].u]+dis[e[i].v][y]-e[i].t);
	base.copy(dis),ans.copy(dis);
	for(;k;k>>=1,base=base*base)if(k&1)ans=ans*base;
	printf("%lld",ans.a[1][n]);
	return 0;
}
posted @ 2021-07-29 20:52  Nesraychan  阅读(203)  评论(0)    收藏  举报