1. 程式人生 > >【DP】【二分圖最大權匹配】DeerInZooDivOne

【DP】【二分圖最大權匹配】DeerInZooDivOne

題意:

給出一棵樹,找出其中兩個互相同構的聯通塊,要求聯通塊儘可能大。
N 50 N\le 50


分析:

首先,要求聯通塊必須分開,可以暴力列舉一條邊,將這條邊刪去,然後在兩側找同構聯通塊。

具體做法可以利用DP:
d

p ( x , f a x , y ,
f a y ) dp(x,fa_x,y,fa_y) 表示:在以x為根, f a
x fa_x
為父親的子樹,與以y為根, f a y fa_y 為父親的子樹,能找到的最大同構聯通塊。

但問題來了,肯定不能暴力列舉 n ! n! 種匹配方案(即找到x的子節點,然後再找一個y的子節點,讓它們互相匹配,再找下一個……)。

這樣肯定T炸。

不過,既然發現是匹配,完全可以利用二分圖最大權匹配來做:
即:對每個x的子節點建一個點,對每個y的子節點建一個點,每兩點之間連一條邊,連結 i > j i->j 的權值為 d p ( i , x , j , y ) dp(i,x,j,y)

這樣每次轉移都跑一發最大權匹配即可。複雜度 O ( n 6 ) O(n^6) ,但其實很大一部分是用不到的。因為每次轉移的點的個數均攤下來是N個,不可能每次轉移都有 N N 個點。

另外,為了求答案,還需要維護一種特殊狀態 d p ( x , n , y , n ) dp(x,n,y,n) 表示分別以x,y為根的最大同構聯通塊。其實不用擔心,這種狀態與其他狀態唯一的不同之處是:這裡建點的時候就是所有相鄰點都建,而其他情況下總有一個父親節點不能建。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#define SF scanf
#define PF printf
#define MAXN 55
#define INF 0x3FFFFFFF
using namespace std;
vector<int> A,B,a[MAXN],b[MAXN],c[MAXN],w[MAXN],rev[MAXN];
void add_edge(int x,int y,int p){
	b[x].push_back(y);
	b[y].push_back(x);
	w[x].push_back(1);
	w[y].push_back(0);
	c[x].push_back(-p);
	c[y].push_back(p);
	rev[x].push_back(b[y].size()-1);
	rev[y].push_back(b[x].size()-1);
}
int las[MAXN],lasid[MAXN],tot;
queue<int> q;
bool inq[MAXN];
int dist[MAXN],s,t,n,m;
bool spfa(){
	for(int i=0;i<tot;i++)
		dist[i]=INF;
	q.push(s);
	dist[s]=0;
	while(!q.empty()){
		int x=q.front();
		q.pop();
		inq[x]=0;
		for(int i=0;i<int(b[x].size());i++){
			int u=b[x][i];
			if(w[x][i]!=0&&dist[u]>dist[x]+c[x][i]){
				dist[u]=dist[x]+c[x][i];
				las[u]=x;
				lasid[u]=i;
				if(inq[u]==0){
					inq[u]=1;
					q.push(u);	
				}
			}
		}
	}
	return (dist[t]!=INF);
}
int maxprice(){
	int maxp=0;
	while(spfa()){
		int flow=INF,add=0;
		for(int i=t;i!=s;i=las[i])
			flow=min(flow,w[las[i]][lasid[i]]);
		for(int i=t;i!=s;i=las[i]){
			add=add+flow*c[las[i]][lasid[i]];
			int u=las[i],id=lasid[i];
			w[u][id]-=flow;
			w[i][rev[u][id]]+=flow;
		}
		maxp+=add;
	}
	return maxp;
}	
int maxmatch(int tra[MAXN][MAXN],int sizA,int sizB){
	tot=sizA+sizB+2;
	for(int i=0;i<tot;i++){
		b[i].clear();
		w[i].clear();
		c[i].clear();
		rev[i].clear();
	}
	s=sizA+sizB;
	t=s+1;
	for(int i=0;i<sizA;i++)
		add_edge(s,i,0);
	for(int i=0;i<sizB;i++)
		add_edge(i+sizA,t,0);
	for(int i=0;i<sizA;i++)
		for(int j=0;j<sizB;j++)
			add_edge(i,j+sizA,tra[i][j]);
	return -maxprice();
}
int dp[MAXN][MAXN][MAXN][MAXN];
int solve(int x,int fx,int y,int fy){
	if(dp[x][fx][y][fy]!=-1)
		return dp[x][fx][y][fy];
	int tra[MAXN][MAXN]={0};
	for(int i=0;i<int(a[x].size());i++){
		if(a[x][i]==fx)
			continue;
		for(int j=0;j<int(a[y].size());j++){
			if(a[y][j]==fy)
				continue;
			tra[i][j]=solve(a[x][i],x,a[y][j],y);
		}	
	}
	dp[x][fx][y][fy]=maxmatch(tra,a[x].size(),a[y].size())+1;
	dp[y][fy][x][fx]=dp[x][fx][y][fy];
	return dp[x][fx][y][fy];
}
int fs[MAXN];
int get_fa(int x){
	if(fs[x]==-1)
		return x;
	fs[x]=get_fa(fs[x]);
	return fs[x];	
}
void merge(int x,int y){
	x=get_fa(x);
	y=get_fa(y);
	fs[x]=y;
}
int main(){
	SF("%d",&m);
	n=m+1;
	A.resize(m);
	B.resize(m);
	for(int i=0;i<m;i++)
		SF("%d",&A[i]);
	for(int i=0;i<m;i++)
		SF("%d",&B[i]);
	int ans=-1;
	for(int del=0;del<m;del++){
		memset(fs,-1,sizeof fs);
		for(int i=0;i<n;i++)
			a[i].clear();
		for(int i=0;i<m;i++){
			if(i==del) continue;
			a[A[i]].push_back(B[i]);
			a[B[i]].push_back(A[i]);
			merge(A[i],B[i]);
		}
		memset(dp,-1,sizeof dp);
		for(int i=0;i<n;i++)
			for(int j=0;j<n;j++)
				if(get_fa(i)!=get_fa(j))				
					ans=max(ans,solve(i,n,j,n));
	}
	//return ans;
	PF("%d\n",ans);
}