【BZOJ4773】负环-倍增+Floyd

测试地址:负环
做法: 本题需要用到倍增+Floyd。
我们很快能想出 O ( n 2 m ) O(n^2m) O(n2m)的算法:令 f ( i , j , k ) f(i,j,k) f(i,j,k)为走 i i i条边,从 j j j走到 k k k的路径中最小的权值和。从小到大枚举 i i i转移即可。
然而并过不了,而且我们发现,负环的长度似乎也不是单调的,即存在长为 k k k的负环,不一定表示存在长为 k + 1 k+1 k+1的负环。实际上,我们只要给每个点加一个边权为 0 0 0的自环,就可以解决这个问题了。
发现这个性质单调后,我们有两种思路:一是二分答案,二是倍增。但我们发现二分答案需要构造走 i i i步时的邻接矩阵(也就是上面写的 f ( i , j , k ) f(i,j,k) f(i,j,k)组成的矩阵),和上面相比复杂度没有区别,因此我们排除这一思路,考虑倍增。
要使用倍增,需要明确一个问题:我们知道行走分两个阶段,得到第一个阶段和第二个阶段的邻接矩阵,如何把它们合并为整个行走的邻接矩阵呢?这时,我们可以使用一种类似Floyd,也类似于矩阵乘法的一个算法,即枚举中间点 k k k,然后枚举整个过程的起点 i i i和终点 j j j转移。我们发现这个东西和矩阵乘法非常类似,它也和矩阵乘法一样满足结合律,但不满足交换律,因此我们采用类似矩阵快速幂的倍增算法,先 O ( n 3 log ⁡ n ) O(n^3\log n) O(n3logn)处理出走 2 i ( i ≥ 0 ) 2^i(i\ge 0) 2i(i0)次的邻接矩阵,然后从高到低枚举 i i i,如果当前行走的矩阵与走 2 i 2^i 2i次的邻接矩阵合并后,图中没有出现负环,则把当前行走的矩阵与走 2 i 2^i 2i次的矩阵合并作为新的当前行走的矩阵,即表示走了 2 i 2^i 2i步。最后,算法中行走的步数 + 1 +1 +1就是答案。注意答案比 n n n大时,显然就表示无解。于是我们就以 O ( n 3 log ⁡ n ) O(n^3\log n) O(n3logn)的时间复杂度解决了这一题。
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
const int inf=1000000000;
int n,m,finalans=0;
struct matrix
{
	int dis[310][310];
}M[10],E,now,ans;

void mult(matrix A,matrix B,matrix &S)
{
	S=E;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			for(int k=1;k<=n;k++)
				S.dis[i][j]=min(S.dis[i][j],A.dis[i][k]+B.dis[k][j]);
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
			E.dis[i][j]=inf;
		E.dis[i][i]=0;
	}
	
	M[0]=E;
	for(int i=1;i<=m;i++)
	{
		int x,y,w;
		scanf("%d%d%d",&x,&y,&w);
		M[0].dis[x][y]=w;
	}
	for(int i=1;i<=9;i++)
		mult(M[i-1],M[i-1],M[i]);
	
	now=E;
	for(int i=9;i>=0;i--)
	{
		mult(now,M[i],ans);
		bool flag=0;
		for(int j=1;j<=n;j++)
			if (ans.dis[j][j]<0)
			{
				flag=1;
				break;
			}
		if (!flag) finalans+=(1<<i),now=ans;
	}
	if (finalans>n) printf("0");
	else printf("%d",finalans+1);
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值