【BZOJ4773】負環-倍增+Floyd

測試地址:負環
做法: 本題需要用到倍增+Floyd。
我們很快能想出O(n2m)O(n^2m)的算法:令f(i,j,k)f(i,j,k)爲走ii條邊,從jj走到kk的路徑中最小的權值和。從小到大枚舉ii轉移即可。
然而並過不了,而且我們發現,負環的長度似乎也不是單調的,即存在長爲kk的負環,不一定表示存在長爲k+1k+1的負環。實際上,我們只要給每個點加一個邊權爲00的自環,就可以解決這個問題了。
發現這個性質單調後,我們有兩種思路:一是二分答案,二是倍增。但我們發現二分答案需要構造走ii步時的鄰接矩陣(也就是上面寫的f(i,j,k)f(i,j,k)組成的矩陣),和上面相比複雜度沒有區別,因此我們排除這一思路,考慮倍增。
要使用倍增,需要明確一個問題:我們知道行走分兩個階段,得到第一個階段和第二個階段的鄰接矩陣,如何把它們合併爲整個行走的鄰接矩陣呢?這時,我們可以使用一種類似Floyd,也類似於矩陣乘法的一個算法,即枚舉中間點kk,然後枚舉整個過程的起點ii和終點jj轉移。我們發現這個東西和矩陣乘法非常類似,它也和矩陣乘法一樣滿足結合律,但不滿足交換律,因此我們採用類似矩陣快速冪的倍增算法,先O(n3logn)O(n^3\log n)處理出走2i(i0)2^i(i\ge 0)次的鄰接矩陣,然後從高到低枚舉ii,如果當前行走的矩陣與走2i2^i次的鄰接矩陣合併後,圖中沒有出現負環,則把當前行走的矩陣與走2i2^i次的矩陣合併作爲新的當前行走的矩陣,即表示走了2i2^i步。最後,算法中行走的步數+1+1就是答案。注意答案比nn大時,顯然就表示無解。於是我們就以O(n3logn)O(n^3\log n)的時間複雜度解決了這一題。
以下是本人代碼:

#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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章