NOI Online #1 入门组 T3 魔法

原题链接

简洁题意

求一个最短路,可以把中间至多 k k k条道路的权改成负的。

分析:

可以发现,本题 n n n的范围很小,可以用 f l o y d floyd floyd来做。 f l o y d floyd floyd求一边最短路, d ( i , j ) = min ⁡ ( d ( i , k ) + d ( k , j ) ) d(i,j)=\min(d(i,k)+d(k,j)) d(i,j)=min(d(i,k)+d(k,j)),我们求出最短路后,考虑加上魔法。设 f ( i , j , p ) f(i,j,p) f(i,j,p) i i i j j j使用 p p p次魔法的最短路。可以把一条道路改成负的,那么就有状态转移方程 f ( i , j , p ) = min ⁡ ( f ( i , u , x ) − e d g e ( u , v ) + f ( v , j , y ) , x + y = p ) f(i,j,p)=\min(f(i,u,x)-edge(u,v)+f(v,j,y),x+y=p) f(i,j,p)=min(f(i,u,x)edge(u,v)+f(v,j,y),x+y=p)这样的状态转移方程空间是不够的,而且要枚举四层循环。如果改成滚动数组, f ( i , j ) f(i,j) f(i,j)表示 i i i j j j的最短路。不考虑用了几次魔法,则: f ( i , j ) = min ⁡ ( f ( i , u ) − e d g e ( u , v ) + f ( v , j ) ) f(i,j)=\min(f(i,u)-edge(u,v)+f(v,j)) f(i,j)=min(f(i,u)edge(u,v)+f(v,j))于是,我们来考虑用 k k k次魔法的问题。很明显,每次迭代都会选择一条边用魔法或者不选,而不选只会是最优解,则这样迭代 k k k次就是至多用 k k k次魔法的解。但是这样空间够了,可时间仍然不够,还是四层循环。
考虑再优化,这样计算了 k k k次,而且这个每次选了边用魔法,但是先选哪条边是没有关系的,所以满足结合律。那么满足结合律,就可以用快速幂解决。因为 f f f是二维数组,则可以用类似矩阵乘法的方法,改成求 min ⁡ \min min即可。这样,先算出用一次魔法的方案,再快速幂算出用 k k k次的方案。
快速幂的时间复杂度是 Θ ( n 3 log ⁡ n ) \Theta(n^3\log n) Θ(n3logn),前面 d p dp dp求最短路是 Θ ( n 3 ) \Theta(n^3) Θ(n3)的, d p dp dp求一次魔法的时间复杂度是 Θ ( n 2 m ) \Theta(n^2m) Θ(n2m)的,总的时间复杂度是 Θ ( n 3 log ⁡ n + n 2 m ) \Theta(n^3\log n+n^2m) Θ(n3logn+n2m)

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int NN=104;
int n,m,k;
ll d[NN][NN],f[NN][NN];
struct node
{
    
    
	int u,v,w;
}edge[2504];
void mul(ll c[][NN],ll a[][NN],ll b[][NN])
{
    
    
	ll t[NN][NN];
	memset(t,0x3f,sizeof(t));
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			for(int k=1;k<=n;k++)
				t[i][j]=min(t[i][j],a[i][k]+b[k][j]);
	memcpy(c,t,sizeof(t));
}
ll ksm()
{
    
    
	while(k)
	{
    
    
		if(k&1)
			mul(d,d,f);
		mul(f,f,f);
		k>>=1;
	}
	return d[1][n];
}
int main()
{
    
    
	scanf("%d%d%d",&n,&m,&k);
	memset(d,0x3f,sizeof(d));
	for(int i=1;i<=n;i++)
		d[i][i]=0;
	for(int i=0;i<m;i++)
	{
    
    
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		edge[i]=(node){
    
    u,v,w};
		d[u][v]=min(d[u][v],1ll*w);
	}
	for(int k=1;k<=n;k++)
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
	memcpy(f,d,sizeof(f));
	for(int k=0;k<m;k++)
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				f[i][j]=min(f[i][j],d[i][edge[k].u]-edge[k].w+d[edge[k].v][j]);
	printf("%lld",ksm());
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_44043668/article/details/108811073