• 2020牛客暑期多校 10J


    2020牛客暑期多校 10J - Identical Trees


    题意

    给定两棵有根节点的同构树,仅编号存在不同

    问至少修改多少个节点的编号能使得两棵树相同

    prob


    限制

    (1leq nleq500)



    思路

    明显,我们需要先检查对应的子树是否匹配(即子树同构),再去计算不同编号的节点数量

    所以首先要对两棵树做一遍树上hash,求出每个节点对应的子树的hash值便于判断是否同构

    通过链式前向星建图,可以获取每个节点的子节点编号及位置

    同时搜索这两棵树,从两根节点开始(O(n^2))枚举当前两节点的子节点,递归判断两子节点对应的子树是否也是同构树

    如果是,则递归来获取匹配后至少要更改多少次编号才能让两棵子树相同

    将获得的值作为边权,连接枚举的两个子节点,进行二分图匹配

    由于要求出最小的交换次数,所以要使得能够直接匹配上的节点尽可能多

    采用网络流解决二分图匹配问题,由于本题存在边权,且又限制两棵树上的某对节点只能匹配一次(流量限制固定)

    所以最终选取最小费用最大流解决该问题

    由于流量固定(所有节点必须匹配),所以模板MCMF会选择费用的边去流

    而我们希望直接匹配上的节点尽可能,所以需要将费用取负,最后将节点数减去MCMF的费用即可作为答案

    (由于每一次递归就需要进行一次网络流,所以最好写成结构体,或者手写栈空间来保证不会冲突)



    代码

    (12ms/1000ms)

    /*
     *  Author  : StelaYuri
     * Language : GNU G++ 14
     */
    
    //#pragma comment(linker,"/STACK:1024000000,1024000000")
    //#pragma GCC optimize(3)
    #include<bits/stdc++.h>
    //#include<unordered_map>
    //#include<ext/pb_ds/assoc_container.hpp>
    //#include<ext/pb_ds/hash_policy.hpp>
    #define closeSync ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
    #define multiCase int T;cin>>T;for(int t=1;t<=T;t++)
    #define rep(i,a,b) for(int i=a;i<=b;i++)
    #define repp(i,a,b) for(int i=a;i<b;i++)
    #define per(i,a,b) for(int i=a;i>=b;i--)
    #define perr(i,a,b) for(int i=a;i>b;i--)
    #define pb push_back
    #define eb emplace_back
    #define mst(a,b) memset(a,b,sizeof(a))
    using namespace std;
    //using namespace __gnu_pbds;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int,int> P;
    const int INF=0x3f3f3f3f;
    const ll LINF=0x3f3f3f3f3f3f3f3f;
    const double eps=1e-12;
    const double PI=acos(-1.0);
    const double angcst=PI/180.0;
    const ll mod=998244353;
    ll max_3(ll a,ll b,ll c){if(a>b&&a>c)return a;if(b>c)return b;return c;}
    ll min_3(ll a,ll b,ll c){if(a<b&&a<c)return a;if(b<c)return b;return c;}
    ll gcd(ll a,ll b){return b==0?a:gcd(b,a%b);}
    ll qmul(ll a,ll b){ll r=0;while(b){if(b&1)r=(r+a)%mod;a=(a+a)%mod;b>>=1;}return r;}
    ll qpow(ll a,ll n){ll r=1;while(n){if(n&1)r=(r*a)%mod;n>>=1;a=(a*a)%mod;}return r;}
    ll qpow(ll a,ll n,ll p){ll r=1;while(n){if(n&1)r=(r*a)%p;n>>=1;a=(a*a)%p;}return r;}
    
    
    const int maxn=1010,N=510,maxm=250000;
    struct MCMF {
        struct E {
            int from, to, cap, v;
            E() {}
            E(int f, int t, int cap, int v) : from(f), to(t), cap(cap), v(v) {}
        };
        int n, m, s, t;
        vector<E> edges;
        vector<int> G[maxn];
        bool inq[maxn];
        int dis[maxn], pre[maxn], a[maxn];
        void init(int _n, int _s, int _t) {
            n = _n; s = _s; t = _t;
            for (int i = 0; i <= n; i++)
                G[i].clear();
            edges.clear();
            m = 0;
        }
        void addedge(int from, int to, int cap, int cost) {
            edges.emplace_back(from, to, cap, cost);
            edges.emplace_back(to, from, 0, -cost);
            G[from].emplace_back(m++);
            G[to].emplace_back(m++);
        }
        bool spfa() {
            for (int i = 0; i <= n; i++) {
                dis[i] = INF;
                pre[i] = -1;
                inq[i] = false;
            }
            dis[s] = 0, a[s] = INF, inq[s] = true;
            queue<int> Q; Q.push(s);
            while (!Q.empty()) {
                int u = Q.front(); Q.pop();
                inq[u] = false;
                for (int& idx: G[u]) {
                    E& e = edges[idx];
                    if (e.cap && dis[e.to] > dis[u] + e.v) {
                        dis[e.to] = dis[u] + e.v;
                        pre[e.to] = idx;
                        a[e.to] = min(a[u], e.cap);
                        if (!inq[e.to]) {
                            inq[e.to] = true;
                            Q.push(e.to);
                        }
                    }
                }
            }
            return pre[t] != -1;
        }
        int solve() {
            int flow = 0, cost = 0;
            while (spfa()) {
                flow += a[t];
                cost += a[t]*dis[t];
                int u = t;
                while (u != s) {
                    edges[pre[u]].cap -= a[t];
                    edges[pre[u] ^ 1].cap += a[t];
                    u = edges[pre[u]].from;
                }
            }
            return cost;
        }
    };
    
    struct node{
        int to,next;
    }e[N],e2[N];
    int root1,root2,cnt1,head1[N],cntt1[N],cnt2,head2[N],cntt2[N];
    ull hashnum[N],hashnum2[N]; //两棵树每个节点对应子树的hash值
    
    void addedge1(int x,int y)
    {
        e[cnt1].to=y;
        e[cnt1].next=head1[x];
        head1[x]=cnt1++;
        cntt1[x]++;
    }
    void addedge2(int x,int y)
    {
        e2[cnt2].to=y;
        e2[cnt2].next=head2[x];
        head2[x]=cnt2++;
        cntt2[x]++;
    }
    
    ull f_hash(int x)
    {
        vector<ull>v;
        for(int i=head1[x];~i;i=e[i].next)
            v.push_back(f_hash(e[i].to));
        sort(v.begin(),v.end());
        ull h=28;
        for(ull i:v)
            h=h*(ull)13131+i;
        hashnum[x]=h;
        return h;
    }
    ull f_hash2(int x)
    {
        vector<ull>v;
        for(int i=head2[x];~i;i=e2[i].next)
            v.push_back(f_hash2(e2[i].to));
        sort(v.begin(),v.end());
        ull h=28;
        for(ull i:v)
            h=h*(ull)13131+i;
        hashnum2[x]=h;
        return h;
    }
    
    int match(int p1,int p2)
    {
        if(head1[p1]==-1&&head2[p2]==-1)
            return (p1==p2?-1:0); //假如这两个节点都没有子树,则直接返回两节点编号是否相同
        
        int bas=cntt1[p1]+cntt2[p2];
        MCMF f;
        f.init(bas+2,bas+1,bas+2); //建立最小费用最大流
        
        for(int i=head1[p1],ii=0;~i;i=e[i].next,ii++)
        {
            f.addedge(f.s,ii,1,0); //源点向树1上节点的子节点连边,流量为1花费为0
            for(int j=head2[p2],jj=0;~j;j=e2[j].next,jj++)
            {
                if(hashnum[e[i].to]==hashnum2[e2[j].to]) //如果两棵树同构
                    f.addedge(ii,jj+cntt1[p1],1,match(e[i].to,e2[j].to)); //建立流量为1花费为子节点匹配结果的边
            }
        }
        for(int j=head2[p2],jj=0;~j;j=e2[j].next,jj++)
            f.addedge(jj+cntt1[p1],f.t,1,0); //树2上节点的子节点向汇点连边,流量为1花费为0
        
        return f.solve()+(p1==p2?-1:0); //这里还得加上对p1和p2编号的判断
    }
    
    void solve()
    {
        mst(head1,-1);
        mst(head2,-1);
        int n,d;
        cin>>n;
        rep(i,1,n)
        {
            cin>>d;
            if(d) addedge1(d,i);
            else root1=i;
        }
        rep(i,1,n)
        {
            cin>>d;
            if(d) addedge2(d,i);
            else root2=i;
        }
        f_hash(root1);
        f_hash2(root2); //先做两遍树上哈希
        cout<<n+match(root1,root2)<<'
    '; //匹配得到的值为负数,表示能够直接匹配的最大对数
    }
    int main()
    {
        closeSync;
        solve();
        return 0;
    }
    

  • 相关阅读:
    Python基础
    SQL脚本
    PDF技术之-jasperreports的使用
    redis缓存和mysql数据库如何保证数据一致性
    理解MySQL的乐观锁,悲观锁与MVCC
    intellj idea创建maven项目一直处于加载的解决问题
    Linux目录详解,软件应该安装到哪个目录
    总结
    总结
    总结
  • 原文地址:https://www.cnblogs.com/stelayuri/p/13471844.html
Copyright © 2020-2023  润新知