• 【2020 牛客多校】第十场 Identical Trees 【树形DP 最大权匹配】


    Identical Trees

    题意

    给出两颗同构树:

    image-20200814084654628

    每次可以修改一个节点值,问最少需要修改多少次,使得两棵树一样。

    错误思路

    比赛的时候直接把两棵树的所有根节点到叶子节点的链提取出来,当做一个二分图,长度相同的链,左边树的链向右边连边,权值为节点编号不同的个数,然后跑最大权匹配。

    没怎么写过最大权,找 bug 找到比赛结束。找完成功WA了。

    如果要连边,那么要考虑到子树同构的问题,如果不是同构是不能连边的

    题解

    树形 DP + 二分图最大权匹配

    思路就是让第一棵树的链匹配第二颗树的链,编号尽可能的相同。

    同时 dfs 两棵树

    匹配之前要检查子树是否同构。

    如果匹配,直接暴力枚举两个子树的儿子节点,接着进行 dfs 。

    获得任意两个儿子匹配之后的边权,跑最大权匹配。

    代码

    /*
     * @Autor: valk
     * @Date: 2020-07-17 16:50:40
     * @LastEditTime: 2020-08-14 09:08:18
     * @Description: 如果邪恶 是 华丽残酷的乐章 它的终场 我会亲手写上 晨曦的光 风干最后一行忧伤 黑色的墨 染上安详
     */
    #include <bits/stdc++.h>
    #define fuck system("pause")
    #define emplace_back push_back
    #define pb push_back
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    const ll mod = 1e9 + 7;
    const ll seed = 12289;
    const double eps = 1e-6;
    const ll inf = 0x3f3f3f3f3f3f3f3f;
    const ll N = 1000 + 10;
    
    //KM 模板
    ll lx[N], ly[N]; //同时调节两个数组,使得权值和最大
    ll n;
    //n1,n2为二分图的顶点集,其中x属于n1,y属于n2
    //link记录n2中的点y在n1中所匹配的x点的编号
    ll link[N];
    ll slack[N]; //松弛操作
    ll visx[N], visy[N];
    bool dfs(vector<vector<ll>>& w, ll x)
    {
        visx[x] = 1; //得到发生矛盾的居民集合
        //对于这个居民,每个房子都试一下,找到就退出
        for (ll y = 1; y <= n; y++) {
            if (visy[y])
                continue; //不需要重复访问
            ll t = lx[x] + ly[y] - w[x][y]; //这个条件下使用匈牙利算法
            if (t == 0) //标志这个房子可以给这个居民
            {
                visy[y] = 1;
                //这个房子没人住或者可以让住着个房子的人去找另外的房子住
                if (link[y] == 0 || dfs(w,link[y])) {
                    link[y] = x;
                    return 1; //可以让这位居民住进来
                }
            } else if (slack[y] > t) //否则这个房子不能给这位居民
                slack[y] = t;
        }
        return 0;
    }
    ll KM(vector<vector<ll>>& w)
    {
        memset(lx, 0, sizeof(lx));
        memset(ly, 0, sizeof(ly));
        memset(link, 0, sizeof(link));
        //首先把每个居民出的钱最多的那个房子给他
        for (ll i = 1; i <= n; i++)
            for (ll j = 1; j <= n; j++)
                if (lx[i] < w[i][j])
                    lx[i] = w[i][j];
    
        //在满足上述条件之后,给第i位居民分配房子
        for (ll i = 1; i <= n; i++) {
            for (ll j = 1; j <= n; j++)
                slack[j] = inf; //松弛量
            while (1) //直到给这个居民找到房子为止
            {
                memset(visx, 0, sizeof(visx));
                memset(visy, 0, sizeof(visy));
                if (dfs(w,i))
                    break; //找到房子,就跳出循环
                ll d = inf;
                for (ll k = 1; k <= n; k++)
                    if (!visy[k] && d > slack[k])
                        d = slack[k]; //找到最小松弛量
                for (ll k = 1; k <= n; k++) //松弛操作,使发生矛盾的居民有更多选择
                {
                    if (visx[k])
                        lx[k] -= d;
                    //将矛盾居民的要求降低,使发生矛盾的居民有更多
    
                    if (visy[k])
                        ly[k] += d;
                    //使发生矛盾的房子在下一个子图,保持矛盾
                }
            }
        }
        ll ans = 0;
        for (ll i = 1; i <= n; i++)
            ans += w[link[i]][i];
        return ans;
    }
    
    vector<ll> v1[N], v2[N];
    ll dp[N][N];//dp[i][j]表示左树以 i 为根节点的子树和右树以 j 为根节点的子树匹配需要修改的最小次数
    
    ll dfs1(ll x, ll y)
    {
        if (dp[x][y] >= 0)
            return dp[x][y];
        if (v1[x].size() != v2[y].size())//不是同构
            return dp[x][y] = (1LL << 30);
        dp[x][y] = (x != y);//当前节点编号不一样,则需要修改
        
        vector<vector<ll>> w(v1[x].size() + 1, vector<ll>(v2[y].size() + 1));//KM 的邻接矩阵
    
        for (ll i = 0; i < v1[x].size(); i++) {
            for (ll j = 0; j < v2[y].size(); j++) {//暴力匹配
                dp[v1[x][i]][v2[y][j]] = dfs1(v1[x][i], v2[y][j]);
                w[i + 1][j + 1] = -dp[v1[x][i]][v2[y][j]]; //建边,因为要求最小所以边权取负
            }
        }
        n = v1[x].size();// KM 的点数
        dp[x][y] -= KM(w);// 加上子树匹配需要修改的最小次数
        return dp[x][y] = min(dp[x][y], (1LL << 30));
    }
    
    int main()
    {
        memset(dp, -1, sizeof(dp));
        ll m;
        scanf("%lld", &m);
        for (ll i = 1; i <= m; i++) {
            ll x;
            scanf("%lld", &x);
            v1[x].pb(i);
        }
        for (ll i = 1; i <= m; i++) {
            ll x;
            scanf("%lld", &x);
            v2[x].pb(i);
        }
        printf("%lld
    ", dfs1(0, 0));
        return 0;
    }
    
  • 相关阅读:
    linux嵌入式系统交叉开发环境
    Codeforces Round #208 E. Dima and Kicks
    mvn 编译错误java.lang.NoSuchMethodError: org.objectweb.asm.ClassWriter. <init>(Z)V
    黑马程序员_<<TCP>>
    微信/易信公共平台开发(四):公众号调试器 (仿真微信平台,提供PHP源码)
    用pdb调试OpenStack Havana
    MySql Odbc等驱动下载地址分享下
    导入exce表格中的数据l到数据库
    关闭数据备份信息写入数据库日志
    SQL Server之RAID简介
  • 原文地址:https://www.cnblogs.com/valk3/p/13500395.html
Copyright © 2020-2023  润新知