两棵树

题目描述

这里写图片描述

树形dp

设f[i][j]表示第一棵树中以i为根的子树和第二棵树中以j为根的子树实现对称所需要的最小代价,而i的儿子x和j的儿子y的f[x][y]的值是已经求出来的,如何转移到f[i][j]

二分图带权匹配

我们要求的其实就是个最小权完备匹配(和最大权类似,把边权取反即可),对于儿子数不同的点对,我们可以直接添点,边的权值即为对应点的size。由于费用流不会,果断选择km算法。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=1000+5;
int son1[maxn][maxn],son2[maxn][maxn],n1[maxn],n2[maxn],w[maxn][maxn],a[maxn],b[maxn],sl[maxn],s1[maxn],s2[maxn],w1[maxn][maxn],f[maxn];
int i,n,t1,t2;
bool bz1[maxn],bz2[maxn];
void df1(int x){
    int i;
    s1[x]=1;
    fo(i,1,n1[x]) {
        df1(son1[x][i]);
        s1[x]+=s1[son1[x][i]];
    }
}
void df2(int x){
    int i;
    s2[x]=1;
    fo(i,1,n2[x]) {
        df2(son2[x][i]);
        s2[x]+=s2[son2[x][i]];
    }
}
bool find(int x,int m,int x2){
    if (bz1[x]) return 0;
    bz1[x]=1;
    int i;
    fo(i,1,m){
        int j=son2[x2][i];
        if (bz2[j]) continue;
        int p=a[x]+b[j]-w1[x][j];
        if (p==0){
            bz2[j]=1;
            if ((!f[j])||(find(f[j],m,x2))){
                f[j]=x;
                return 1;
            } 
        }else sl[j]=min(sl[j],p);
    }
    return 0;
}
void dfs(int x1,int x2){
    if ((s1[x1]==0)||(s2[x2]==0)){
        w[x1][x2]=s1[x1]+s2[x2];
        return;
    } 
    int i,j,m=max(n1[x1],n2[x2]);
    if (m==0) {
        w[x1][x2]=0;
        return;
    }
    if (n1[x1]<m) {
        fo(i,n1[x1]+1,m) if (!son1[x1][i])son1[x1][i]=++t1;
    }else fo(i,n2[x2]+1,m) if (!son2[x2][i]) son2[x2][i]=++t2;
    fo(i,1,m)
    fo(j,1,m) {
        dfs(son1[x1][i],son2[x2][j]);
        w1[son1[x1][i]][son2[x2][j]]=-w[son1[x1][i]][son2[x2][j]];
    }   
    fo(i,1,m) {
        a[son1[x1][i]]=-10000;
        f[son2[x2][i]]=0;
        fo(j,1,m) a[son1[x1][i]]=max(a[son1[x1][i]],w1[son1[x1][i]][son2[x2][j]]);
    }
    fo(i,1,m){
        fo(j,1,m) sl[son2[x2][j]]=10000;
        while (1){
            fo(j,1,m) bz1[son1[x1][j]]=0;
            fo(j,1,m) bz2[son2[x2][j]]=0;
            if (find(son1[x1][i],m,x2)) break;
            int d=10000;
            fo(j,1,m) if (!bz2[son2[x2][j]]) d=min(d,sl[son2[x2][j]]);
            fo(j,1,m) {
                if (bz1[son1[x1][j]]) a[son1[x1][j]]-=d;
                if (bz2[son2[x2][j]]) b[son2[x2][j]]+=d;else sl[son2[x2][j]]-=d;
            }
        }
    }
    w[x1][x2]=0;
    fo(i,1,m) w[x1][x2]=w[x1][x2]-w1[f[son2[x2][i]]][son2[x2][i]];
}
int main(){
    scanf("%d",&n);t1=n+1;
    fo(i,2,n+1){
        int x;
        scanf("%d",&x);x++;
        son1[x][++n1[x]]=i;
    }
    scanf("%d",&n);t2=n+1;
    fo(i,2,n+1){
        int x;
        scanf("%d",&x);x++;
        son2[x][++n2[x]]=i;
    }
    df1(1),df2(1);
    dfs(1,1);
    printf("%d\n",w[1][1]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值