1. 程式人生 > >【BZOJ3168】[Heoi2013]鈣鐵鋅硒維生素 高斯消元求矩陣的逆+匈牙利算法

【BZOJ3168】[Heoi2013]鈣鐵鋅硒維生素 高斯消元求矩陣的逆+匈牙利算法

def strong bzoj light sof turn 防止 宇宙 !=

【BZOJ3168】[Heoi2013]鈣鐵鋅硒維生素

Description

銀河隊選手名單出來了!小林,作為特聘的營養師,將負責銀河隊選手參加宇宙比賽的飲食。眾所周知,前往宇宙的某個星球,通常要花費好長好長的時間,人體情況在這之間會發生變化,因此,需要根據每天的情況搭配夥食,來保證營養。小林把人體需要的營養分成了n種,這些營養包括但不限於鐵,鈣。他準備了2套廚師機器人,一套廚師機器人有n個,每個廚師機器人只會做一道菜,這道菜一斤能提供第i種營養xi微克。想要吃這道菜的時候,只要輸入一個數,就能吃到對應數量的這道菜了。為防止攝入過量對身體造成的傷害,每個機器人還有防過量攝入藥,只要輸入一個數,就能生成一定劑量的藥,吃了這些藥,就能減少相當於食用對應數目的這道菜提供的營養。小林之所以準備2套廚師機器人,正是因為旅途漫漫,難以預計,也許某一個廚師機器人在途中壞掉,要是影響了銀河隊選手的身體,就不好了。因此,第2套廚師機器人被用來做第1套的備用。小林需要為每一個第1套廚師機器人選一個第2套廚師機器人作備份,使得當這個機器人壞掉時,用備份頂替,整套廚師機器人仍然能搭配出任何營養需求,而且,每個第2套廚師機器人只能當一個第1套廚師機器人的備份。

Input

第一行包含一個正整數n。 接下來n行,每行n個整數,表示第1套廚師機器人做的菜每一斤提供的每種營養。 再接下來n行,每行n個整數,表示第2套廚師機器人做的菜每一斤提供的每種營養。 1≤n≤300,所有出現的整數均非負,且不超過10,000。

Output

第一行是一個字符串,如果無法完成任務,輸出“NIE”,否則輸出“TAK” 並跟著n行,第i行表示第i個第1套機器人的備份是哪一個第2套機器人。 為了避免麻煩,如果有多種可能的答案,請給出字典序最小的那一組。

Sample Input

3
1 0 0
0 1 0
0 0 1
2 3 0
0 7 8
0 0 9

Sample Output

TAK
1
2
3

題解:要不是有大爺的題解我還真就不一定能看懂題意。。。

題目大意:給定一個nn的滿秩矩陣A和一個nn的矩陣B,求一個字典序最小的1...n的排列a滿足將任意一個Ai換成Bai後矩陣A仍然滿秩。

是不是清晰多了?求這樣的排列實際上是將A乘上一個矩陣C使得CA=B,C=AB-1

求出來C後,我們改變思路,將用邊i->j表示B中j號機器人能替換A中i號機器人,然後就得到了一個二分圖,而CT正好是二分圖的鄰接矩陣,我們想求二分圖的字典序最小的完美匹配。

一開始以為大爺做麻煩了,直接從後往前做一遍匈牙利算法就行,知道我看了Discuss。。。好吧還是要做兩邊,第一遍正常跑,第二遍貪心的選編號小的,並且要求後面的點不能影響前面的點。

這裏依舊是做一下大爺博客的註釋:

1.為什麽是CA=B而不是AC=B???(我一開始也死活搞不懂)
因為每個機器人對應一個行向量,而我們想讓A中的行向量對應B中另外一個行向量,這顯然直接矩乘是不行的,但如果我們將A,B分別轉置,就可以做到A中一個列向量對應B中另外一個列向量,然後就是ATC=BT(這裏的C指的就是二分圖的鄰接矩陣),等價於CTA=B

2.不影響前面的交錯環是什麽鬼?(或者你認為我說的也不是那麽清楚?)

直接說方法吧:在第二遍DFS的時候記錄一下當前是從那個點開始去尋找增廣路的,也就意味著編號比這個點小的點我們都不能影響。所以我們看一下B中的機器人所對應的A中的機器人的編號,如果編號<起始點,就不能更改;如果編號=起始點,而我們已經向讓起始點和其它點匹配了,這個點就已經被閑置出來了,就可以更改;如果編號>起始點,那麽我們繼續尋找增廣路就行了。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
#define mod 999911657
using namespace std;
const double eps=1e-6;
typedef long long ll;
int n,ans;
ll A[310][610],B[310][310],C[310][310];
int vis[310],from[310],to[310];
ll pm(ll x,ll y)
{
	ll z=1;
	while(y)
	{
		if(y&1)	z=z*x%mod;
		x=x*x%mod,y>>=1;
	}
	return z;
}
int rd()
{
	int ret=0;	char gc=getchar();
	while(gc<‘0‘||gc>‘9‘)	gc=getchar();
	while(gc>=‘0‘&&gc<=‘9‘)	ret=ret*10+gc-‘0‘,gc=getchar();
	return ret;
}
int dfs1(int x)
{
	for(int i=1;i<=n;i++)
	{
		if(C[i][x]&&!vis[i])
		{
			vis[i]=1;
			if(!from[i]||dfs1(from[i]))
			{
				to[x]=i,from[i]=x;
				return 1;
			}
		}
	}
	return 0;
}
int dfs2(int x,int y)
{
	for(int i=1;i<=n;i++)
	{
		if(C[i][x]&&!vis[i])
		{
			vis[i]=1;
			if(from[i]==y||(from[i]>y&&dfs2(from[i],y)))
			{
				from[i]=x,to[x]=i;
				return 1;
			}
		}
	}
	return 0;
}
int main()
{
	n=rd();
	int i,j,k;
	for(i=1;i<=n;i++)	for(j=1;j<=n;j++)	A[i][j]=rd(),A[i][j+n]=(i==j);
	for(i=1;i<=n;i++)	for(j=1;j<=n;j++)	B[i][j]=rd();
	ll t;
	for(i=1;i<=n;i++)
	{
		for(j=i;j<=n;j++)	if(A[j][i])
		{
			for(k=1;k<=2*n;k++)	swap(A[i][k],A[j][k]);
			break;
		}
		t=pm(A[i][i],mod-2);
		for(k=i;k<=2*n;k++)	A[i][k]=A[i][k]*t%mod;
		for(j=1;j<=n;j++)	if(i!=j)
		{
			t=A[j][i];
			for(k=1;k<=2*n;k++)	A[j][k]=(A[j][k]-t*A[i][k]%mod+mod)%mod;
		}
	}
	for(i=1;i<=n;i++)	for(j=1;j<=n;j++)	for(k=1;k<=n;k++)	C[i][j]=(C[i][j]+B[i][k]*A[k][j+n])%mod;
	for(i=1;i<=n;i++)
	{
		memset(vis,0,sizeof(vis));
		ans+=dfs1(i);
	}
	if(ans<n)
	{
		printf("NIE");
		return 0;
	}
	printf("TAK\n");
	for(i=1;i<=n;i++)
	{
		memset(vis,0,sizeof(vis));
		dfs2(i,i);
	}
	for(i=1;i<=n;i++)	printf("%d\n",to[i]);
	return 0;
}

【BZOJ3168】[Heoi2013]鈣鐵鋅硒維生素 高斯消元求矩陣的逆+匈牙利算法