1. 程式人生 > 實用技巧 >【二分圖】HEOI2012 朋友圈

【二分圖】HEOI2012 朋友圈

題目內容

洛谷連結
在很久很久以前,曾經有兩個國家和睦相處,無憂無慮的生活著。

一年一度的評比大會開始了,作為和平的兩國,一個朋友圈數量最多的永遠都是最值得他人的尊敬,所以現在就是需要你求朋友圈的最大數目。兩個國家看成是AB兩國,現在是兩個國家的描述:

\(A\)國:每個人都有一個友善值,當兩個\(A\)國人的友善值\(a,b\),如果\(a\text{ xor}\text{ }b \bmod 2=1\),那麼這兩個人都是朋友,否則不是;

\(B\)國:每個人都有一個友善值,當兩個\(B\)國人的友善值\(a,b\),如果\(a\text{ xor}\text{ }b \bmod 2=0\)或者(\(a\text{ or}\text{ }b\)

)化成二進位制有奇數個\(1\),那麼兩個人是朋友,否則不是朋友;

\(A、B\)兩國之間的人也有可能是朋友,資料中將會給出\(A、B\)之間“朋友”的情況。 對於朋友的定義,關係是是雙向的。 在AB兩國,朋友圈的定義:一個朋友圈集合 \(S\),滿足\(\subset A \cup B\)對於所有的\(i,j \in S\)\(i\)\(j\)是朋友。

由於落後的古代,沒有電腦這個也就成了每年最大的難題,而你能幫他們求出最大朋友圈的人數嗎?

輸入格式

第一行一個整數\(T(T\leq 6)\),表示輸入資料總數。

對於每組資料:

第一行三個整數 \(A,B,M\),分別表示\(A\)國人數,\(B\)

國人數,\(AB\)兩國之間是朋友的對數。

第二行\(A\)個數\(a_i\)​,表示A國第\(i\)個人的友善值。

第三行\(B\)個數\(b_i\),表示B國第\(i\)個人的友善值。

\(4\)到第\(3+M\)行,每行兩個整數\(x,y\)表示\(A\)國的第\(x\)個人和\(B\)國第\(y\)個人是朋友。

輸出格式

輸出\(T\)行,每行輸出一個整數,表示最大朋友圈的數目。

資料範圍

友善值為int型別正整數。

有兩類資料:

第一類:\(|A| \leq 200, |B| \leq 200\)

第二類:\(|A| \leq 10, |B| \leq 3000\)

樣例輸入

1
2 4 7
1 2
2 6 5 4
1 1
1 2
1 3
2 1
2 2
2 3
2 4

樣例輸出

5

最大朋友圈包含\(A\)國第\(1,2\)人和\(B\)國第\(1,2,3\)人。

思路

先吐槽:因為按位異或和按位與的優先順序調這個破題一下午,謝謝有被噁心到。

此題一看就可知道是一個求最大團的問題,然而一般無向圖的求最大團是一個\(NPC\)問題,況且看到其資料範圍就可以棄了。所以我們要分析一下其中的性質。

先看\(B\)國,可以看出其為一些奇數點和偶數點,況且其中存在一些連邊。是二分圖既視感。不過二分圖是兩邊的部點不存在連邊,所以我們需要建一個關於\(B\)國的補圖。同時補圖的最小獨立集就是原圖的最大團,於是\(B\)國直接建補圖跑最大獨立集即可。

再看\(A\)國,其要求可理解為選出的人要求兩兩奇偶不同,所以\(A\)國只能選出\(0、1、2\)人,再看\(B\)國中和這幾個人有關係的跑最大獨立集,我們直接暴力把所有情況取個\(\max\)即可,記得最大獨立集\(=n-\)最大匹配數。

然後你快樂的連邊之後一頓非常巨的操作跑匈牙利寫完了發現\(T\)了幾個點。(然後並不會\(Dinic\)),所以這時候就需要時間戳優化匈牙利。

匈牙利中佔了時間效率很大的一塊就是memset,每次都要memset理論每次都是\(O(n)\)的效率(當然肯定要小一點),那麼每匹配一次都是\(O(n^2)\)的,這個題要求多次匹配豈不是直接掛了。

所以時間戳優化出現了!其實根本沒那麼高深,設一個時間戳為\(\text{Clock}\),原來的布林型別陣列就改為整數型別,轉化如下:

\[vis[v]=1\rightarrow vis[v]=\text{Clock} \]

\[vis[v]=0\rightarrow vis[v]\not=\text{Clock} \]

每次Clock++,即可\(O(1)\)初始化。

然後就愉快的跑就完事了才不,這個毒瘤出題人居然卡常,跑匈牙利的函式裡的那個迴圈必須加register才能過(大資料開了\(\text{200ms}\)),否則卡線\(TLE\)

其他沒啥了。

程式碼

#include <bits/stdc++.h>
using namespace std;
const int maxn=3000+10;
const int maxm=2e6+10;
int totA,totB,M,ans=-1;
int a[maxn],b[maxn];
bool g[maxn][maxn];

struct Edge{
	int from,to,nxt;
}e[maxm];

inline int read(){
	int x=0,fopt=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')fopt=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=(x<<3)+(x<<1)+ch-48;
		ch=getchar();
	}
	return x*fopt;
}

int head[maxm],cnt;
inline void add(int u,int v){
	e[++cnt].from=u;
	e[cnt].to=v;
	e[cnt].nxt=head[u];
	head[u]=cnt;
}

int Time1,Time2;//時間戳,一個用於vis陣列,一個用於標記朋友關係
int vis[maxn],match[maxn],flag[maxn];
bool dfs(int u){
	for(register int i=head[u];i;i=e[i].nxt){//實測只有這加register最快,別的加上反而慢了
		int v=e[i].to;
		if(vis[v]!=Time1&&flag[v]==Time2){
			vis[v]=Time1;
			if(!match[v]||dfs(match[v])){
				match[v]=u;
				return 1;
			}
		}
	}
	return 0;
}

inline int Count(int x){//數二進位制1的個數
	int res=0;
	while(x){
		if(x&1)res++;
		x>>=1;
	}
	return res;
}

inline void SolveA(){
	int sum=0;
	for(int i=1;i<=totB;i++)
		if(b[i]&1){
			Time1++;
			if(dfs(i))sum++;
		}//選0個,直接對B跑匹配
	ans=max(ans,totB-sum);
	for(int i=1;i<=totA;i++){
		int tot=0;sum=0;Time2++;
		memset(match,0,sizeof(match));
		for(int j=1;j<=totB;j++)
			if(g[i][j+totA]){
				flag[j]=Time2;tot++;//是朋友則flag[j]=1
			}
		for(int j=1;j<=totB;j++)
			if((b[j]&1)&&flag[j]==Time2){
				Time1++;
				if(dfs(j))sum++;
			}
		ans=max(ans,tot-sum+1);//選1個,記得加上選的那個1
	}
	for(int i=1;i<=totA;i++)
		for(int j=i+1;j<=totA;j++){
			if((a[i]^a[j])&1){//記得加括號,否則你會後悔的
				int tot=0;sum=0;Time2++;
				memset(match,0,sizeof(match));
				for(int k=1;k<=totB;k++)
					if(g[i][k+totA]&&g[j][k+totA]){
						flag[k]=Time2;tot++;
					}
				for(int k=1;k<=totB;k++)
					if((b[k]&1)&&flag[k]==Time2){
						Time1++;
						if(dfs(k))sum++;
					}
				ans=max(ans,tot-sum+2);//同理,選2個
			}
		}
}

inline void SolveB(){
	for(int i=1;i<=totB;i++)
		if(b[i]&1){//建補圖,即取條件不符合的
			for(int j=1;j<=totB;j++){
				if(!(b[j]&1)&&!(Count(b[i]|b[j])&1))
					add(i,j);
			}
		}
}

int main(){
	int T=read();
	while(T--){
		totA=read(),totB=read(),M=read();
		for(int i=1;i<=totA;i++)
			a[i]=read();
		for(int i=1;i<=totB;i++)
			b[i]=read();
		SolveB();
		for(int i=1;i<=M;i++){
			int u=read(),v=read();
			g[u][v+totA]=g[v+totA][u]=1;//記錄朋友關係
		}
		SolveA();
		printf("%d\n",ans);
	}
	return 0;
}