1. 程式人生 > 其它 >[NOI Online 2020 #1]魔法 題解

[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;
}