zoukankan      html  css  js  c++  java
  • 【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;
    }
    
  • 相关阅读:
    hadoop中namenode发生故障的处理方法
    开启虚拟机所报的错误:VMware Workstation cannot connect to the virtual machine. Make sure you have rights to run the program, access all directories the program uses, and access all directories for temporary fil
    Hbase的安装与部署(集群版)
    分别用反射、编程接口的方式创建DataFrame
    用Mapreduce求共同好友
    SparkSteaming中直连与receiver两种方式的区别
    privot函数使用
    Ajax无刷新显示
    使用ScriptManager服务器控件前后台数据交互
    数据库知识
  • 原文地址:https://www.cnblogs.com/valk3/p/13500395.html
Copyright © 2011-2022 走看看