1. 程式人生 > >【BZOJ4773】負環-倍增+Floyd

【BZOJ4773】負環-倍增+Floyd

測試地址:負環
做法: 本題需要用到倍增+Floyd。
我們很快能想出 O ( n 2 m ) O(n^2m)

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