题意:
给出一棵树,找出其中两个互相同构的联通块,要求联通块尽可能大。
N
≤
50
N\le 50
N≤50
分析:
首先,要求联通块必须分开,可以暴力枚举一条边,将这条边删去,然后在两侧找同构联通块。
具体做法可以利用DP:
d
p
(
x
,
f
a
x
,
y
,
f
a
y
)
dp(x,fa_x,y,fa_y)
dp(x,fax,y,fay)表示:在以x为根,
f
a
x
fa_x
fax为父亲的子树,与以y为根,
f
a
y
fa_y
fay为父亲的子树,能找到的最大同构联通块。
但问题来了,肯定不能暴力枚举 n ! n! n!种匹配方案(即找到x的子节点,然后再找一个y的子节点,让它们互相匹配,再找下一个……)。
这样肯定T炸。
不过,既然发现是匹配,完全可以利用二分图最大权匹配来做:
即:对每个x的子节点建一个点,对每个y的子节点建一个点,每两点之间连一条边,链接
i
−
>
j
i->j
i−>j的权值为
d
p
(
i
,
x
,
j
,
y
)
dp(i,x,j,y)
dp(i,x,j,y)。
这样每次转移都跑一发最大权匹配即可。复杂度 O ( n 6 ) O(n^6) O(n6),但其实很大一部分是用不到的。因为每次转移的点的个数均摊下来是N个,不可能每次转移都有 N N N个点。
另外,为了求答案,还需要维护一种特殊状态 d p ( x , n , y , n ) dp(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);
}