题目传送门
读完题目后,一个非常简单直观的想法就是把每一颗子树中的标记都对应的比较一遍,寻找拥有最多的相同标记的子树,然后逐层向上合并到根节点,最终答案便是树上的总结点数减去相同标记数。
由此可以想到用dp来表示这一状态。设 dp[u1][u2] 表示T1中以u1为根的子树与T2中以u2为根的子树内相同标记的最大数量。那么对于两颗树内的结点而言共有两种情况:
1、u1、u2是叶子结点,那么只需要判断u1和u2的标记是否相同即可
2、u1、u2不是叶子结点,那么此时的 dp[u1][u2] 就等于所有以这两个根节点的儿子为根的子树中相同标记的最大数量再加上u1和u2结点本身的匹配情况。
容易发现 ,第二种状态的转移过程与二分图最大权匹配的过程十分相似,假设u1的某个儿子为son1,u2的某个儿子为son2,那么我们只要从son1向son2连一条边,将边权设为 dp[son1][son2] ,就可以在图上表示出son1和son2之间相同标记的最大数量。在对u1和u2的每一个儿子都进行这样的操作后,跑一遍二分图最大权匹配的板子即可得到答案。
需要注意的是,比较两棵树之间的标记是否相同的前提是这两棵树的构形必须相同,所以我们需要用树hash判断两棵树的构形,若hash值相同则一样,否则不同。
关于树hash的原理以及模板,大家自行百度。
下面上代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 510;
const ll INF = 0x7f7f7f7f,base = 23;
struct edge{
int to,nxt,cap;
ll flow,cost;
}e[maxn*maxn<<2];
int head[maxn<<1],pre[maxn<<1],cnt,n,m,s,t,root1,root2;
ll flow[maxn<<1],dis[maxn<<1],dp[maxn][maxn],cost;
unsigned int Hash[2][maxn];
bool vis[maxn<<1];
vector<int> tree[2][maxn];
void addEdge(int u,int v,int cap,int cost){
e[cnt].to = v;
e[cnt].nxt = head[u];
e[cnt].cap = cap;
e[cnt].flow = 0;
e[cnt].cost = cost;
head[u] = cnt++;
}
bool SPFA(int s,int t){
queue<int> q;
for(int i=1;i<=n*2+2;++i){
dis[i] = flow[i] = INF;
pre[i] = -1;
vis[i] = false;
}
q.push(s);
vis[s] = true;
dis[s] = 0;
while(!q.empty()){
int u = q.front();
q.pop();
vis[u] = false;
for(int i=head[u];i!=-1;i=e[i].nxt){
int v = e[i].to;
if(e[i].cap>e[i].flow&&dis[v]>dis[u]+e[i].cost){
dis[v] = dis[u]+e[i].cost;
flow[v] = min(flow[u],e[i].cap-e[i].flow);
pre[v] = i;
if(!vis[v]){
q.push(v);
vis[v] = true;
}
}
}
}
return pre[t]!=-1;
}
ll minCostmaxFlow(int s,int t,ll &cost){
ll f = 0;
while(SPFA(s,t)){
for(int u=t;u!=s;u=e[pre[u]^1].to){
e[pre[u]].flow += flow[t];
e[pre[u]^1].flow -= flow[t];
cost += e[pre[u]].cost*flow[t];
}
f += flow[t];
}
return f;
}
void getHash(int u,bool pos){//树hash模板
Hash[pos][u] = 1;
vector<unsigned int> ss;
for(int i=0;i<tree[pos][u].size();++i){
int v = tree[pos][u][i];
getHash(v,pos);
ss.push_back(Hash[pos][v]);
}
sort(ss.begin(),ss.end());
for(int i=0;i<ss.size();++i){
Hash[pos][u] += Hash[pos][u]*base+ss[i];
}
}
void dfs(int u1,int u2){
//若构形不同直接退出,匹配值设为负无穷大
if(Hash[0][u1]!=Hash[1][u2]){
dp[u1][u2] = -INF;
return;
}
//进入子树前先判断根节点标记是否相同
if(u1==u2) dp[u1][u2] = 1;
if(tree[0][u1].size()==0) return;
for(int i=0;i<tree[0][u1].size();++i){
for(int j=0;j<tree[1][u2].size();++j){
int v1 = tree[0][u1][i],v2 = tree[1][u2][j];
dfs(v1,v2);
}
}
for(int i=0;i<tree[0][u1].size();++i){
for(int j=0;j<tree[1][u2].size();++j){
int s1 = tree[0][u1][i],s2 = tree[1][u2][j];
addEdge(s1,s2+n,1,-dp[s1][s2]);
addEdge(s2+n,s1,0,dp[s1][s2]);
}
}
ll res = 0;
minCostmaxFlow(s,t,res);
//跑完一趟最小费用最大流记得一定初始化QAQ
for(int i=0;i<tree[0][u1].size();++i){
int s1 = tree[0][u1][i];
head[s1] = -1;
}
for(int i=0;i<tree[0][u2].size();++i){
int s2 = tree[0][u2][i];
head[s2] = -1;
}
for(int i=head[t];i!=-1;i=e[i].nxt){
if(e[i].flow){
e[i].flow = 0;
e[i^1].flow = 0;
}
}
for(int i=head[s];i!=-1;i=e[i].nxt){
if(e[i].flow){
e[i].flow = 0;
e[i^1].flow = 0;
}
}
dp[u1][u2] -= res;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n*2+2;++i){
head[i] = -1;
}
s = n*2+1;
t = n*2+2;
for(int i=1;i<=n;++i){
int p;
scanf("%d",&p);
if(!p){
root1 = i;
continue;
}
else tree[0][p].push_back(i);
}
for(int i=1;i<=n;++i){
int p;
scanf("%d",&p);
if(!p){
root2 = i;
continue;
}
else tree[1][p].push_back(i);
}
getHash(root1,0);
getHash(root2,1);
for(int i=1;i<=n;++i){
addEdge(s,i,1,0);
addEdge(i,s,0,0);
addEdge(i+n,t,1,0);
addEdge(t,i+n,0,0);
}
dfs(root1,root2);
printf("%lld\n",1LL*n-dp[root1][root2]);
return 0;
}
有任何疑问欢迎提出