【DP】【二分图最大权匹配】DeerInZooDivOne

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34454069/article/details/83049717

题意:

给出一棵树,找出其中两个互相同构的联通块,要求联通块尽可能大。
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);
}

猜你喜欢

转载自blog.csdn.net/qq_34454069/article/details/83049717