Bzoj3197: [Sdoi2013]assassin

题面

传送门

Sol

套路:找出重心,如果有两个就新建一个点
然后把这棵树hash一下
设\(f[i][j]\)表示第一颗树到\(i\)第二棵树到\(j\),子树\(i,j\)同构的付出的最小代价
转移:每次把这一层hash值相同的点做一边二分图权匹配(KM/费用流)就好了
一遍AC

# include <bits/stdc++.h>
# define IL inline
# define RG register
# define Fill(a, b) memset(a, b, sizeof(a))
using namespace std;
typedef long long ll;

IL int Input(){
    RG int x = 0, z = 1; RG char c = getchar();
    for(; c < '0' || c > '9'; c = getchar()) z = c == '-' ? -1 : 1;
    for(; c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) + (c ^ 48);
    return x * z;
}

const int maxn(1005);
const int inf(1e9);
const ll seed1(9377);
const ll seed2(10007);

namespace MCMF{
    int dis[maxn], pre1[maxn], pre2[maxn], vis[maxn], first[maxn], cnt, ans, s, t;
    queue <int> q;

    struct Edge{
        int to, next, f, w;
    } edge[maxn * maxn * 2];

    IL void Init(RG int a, RG int b){
        s = a, t = b, ans = cnt = 0;
        for(RG int i = a; i <= b; ++i) first[i] = -1;
    }

    IL void Add(RG int u, RG int v, RG int f, RG int w){
        edge[cnt] = (Edge){v, first[u], f, w}, first[u] = cnt++;
        edge[cnt] = (Edge){u, first[v], 0, -w}, first[v] = cnt++;
    }

    IL int Aug(){
        for(RG int i = s; i <= t; ++i) dis[i] = inf;
        q.push(s), dis[s] = 0, vis[s] = 1;
        while(!q.empty()){
            RG int u = q.front(); q.pop();
            for(RG int e = first[u]; e != -1; e = edge[e].next){
                RG int v = edge[e].to;
                if(edge[e].f && dis[v] > dis[u] + edge[e].w){
                    dis[v] = dis[u] + edge[e].w;
                    pre1[v] = e, pre2[v] = u;
                    if(!vis[v]) q.push(v), vis[v] = 1;
                }
            }
            vis[u] = 0;
        }
        if(dis[t] == inf) return 0;
        RG int ret = inf;
        for(RG int p = t; p; p = pre2[p]) ret = min(ret, edge[pre1[p]].f);
        ans += ret * dis[t];
        for(RG int p = t; p; p = pre2[p])
            edge[pre1[p]].f -= ret, edge[pre1[p] ^ 1].f += ret;
        return 1;
    }

    IL int Calc(){
        for(ans = 0; Aug(); );
        return ans;
    }
}

int cnt, first[maxn];

struct Edge{
    int to, next;
} edge[maxn << 1];

namespace Hash{
    struct Val{
        unsigned long long f1, f2;

        IL int operator ==(RG Val b) const{
            return f1 == b.f1 && f2 == b.f2;
        }
    } v[maxn];

    IL int Cmp(RG int a, RG int b){
        return v[a].f1 != v[b].f1 ? v[a].f1 < v[b].f1 : v[a].f2 < v[b].f2;
    }

    int size[maxn];

    IL void GetHash(RG int u, RG int ff){
        vector <int> son; son.clear(), size[u] = 1;
        for(RG int e = first[u]; e != -1; e = edge[e].next){
            RG int v = edge[e].to;
            if(v != ff){
                GetHash(v, u);
                size[u] += size[v], son.push_back(v);
            }
        }
        sort(son.begin(), son.end(), Cmp);
        for(RG int i = 0, l = son.size(); i < l; ++i){
            v[u].f1 = v[u].f1 * seed1 + v[son[i]].f1 * seed1 + v[son[i]].f2 * seed2;
            v[u].f2 = v[u].f2 * seed2 + v[son[i]].f1 * seed2 + v[son[i]].f2 * seed1;
        }
        v[u].f1 = v[u].f1 * seed1 + size[u];
        v[u].f2 = v[u].f2 * seed2 + size[u];
    }
};

int n, size[maxn], c1[maxn], c2[maxn], rt, tmp;
int f[maxn][maxn];

IL void Add(RG int u, RG int v){
    edge[cnt] = (Edge){v, first[u]}, first[u] = cnt++;
}

IL void GetRoot(RG int u){
    size[u] = 1; RG int mx = 0;
    for(RG int e = first[u]; e != -1; e = edge[e].next){
        RG int v = edge[e].to;
        if(!size[v]){
            GetRoot(v);
            size[u] += size[v];
            mx = max(mx, size[v]);
        }
    }
    mx = max(mx, n - size[u]);
    if((mx << 1) <= n){
        if(!rt) rt = u;
        else tmp = u;
    }
}

IL void Solve(RG int u1, RG int u2, RG int ff1, RG int ff2){
    f[u1][u2] = c1[u1] != c2[u2];
    vector <int> son1, son2; son1.clear(), son2.clear();
    for(RG int e = first[u1]; e != -1; e = edge[e].next)
        if(edge[e].to != ff1) son1.push_back(edge[e].to);
    for(RG int e = first[u2]; e != -1; e = edge[e].next)
        if(edge[e].to != ff2) son2.push_back(edge[e].to);
    RG int l1 = son1.size(), l2 = son2.size(), s = 0, t = l1 + l2 + 1;
    for(RG int i = 0; i < l1; ++i)
        for(RG int j = 0; j < l2; ++j)
            if(Hash::v[son1[i]] == Hash::v[son2[j]]) Solve(son1[i], son2[j], u1, u2);
    MCMF::Init(0, t);
    for(RG int i = 0; i < l1; ++i)
        for(RG int j = 0; j < l2; ++j)
            if(Hash::v[son1[i]] == Hash::v[son2[j]]) MCMF::Add(i + 1, j + 1 + l1, 1, f[son1[i]][son2[j]]);
    for(RG int i = 0; i < l1; ++i) MCMF::Add(s, i + 1, 1, 0);
    for(RG int i = 0; i < l2; ++i) MCMF::Add(i + 1 + l1, t, 1, 0);
    RG int ret = MCMF::Calc();
    f[u1][u2] += ret;
}

int main(){
    n = Input();
    for(RG int i = 1; i <= n + 1; ++i) first[i] = -1;
    for(RG int i = 1; i < n; ++i){
        RG int u = Input(), v = Input();
        Add(u, v), Add(v, u);
    }
    for(RG int i = 1; i <= n; ++i) c1[i] = Input();
    for(RG int i = 1; i <= n; ++i) c2[i] = Input();
    GetRoot(1);
    if(tmp){
        for(RG int e = first[rt]; e != -1; e = edge[e].next)
            if(edge[e].to == tmp) edge[e].to = n + 1;
        for(RG int e = first[tmp]; e != -1; e = edge[e].next)
            if(edge[e].to == rt) edge[e].to = n + 1;
        Add(n + 1, rt), Add(n + 1, tmp), rt = n + 1;
    }
    Hash::GetHash(rt, 0);
    Solve(rt, rt, 0, 0);
    printf("%d\n", f[rt][rt]);
    return 0;
}

原文地址:https://www.cnblogs.com/cjoieryl/p/9113950.html

时间: 2024-11-13 08:43:49

Bzoj3197: [Sdoi2013]assassin的相关文章

【BZOJ3197】[Sdoi2013]assassin 树同构+动态规划+KM

[BZOJ3197][Sdoi2013]assassin Description Input Output Sample Input 4 1 2 2 3 3 4 0 0 1 1 1 0 0 0 Sample Output 1 HINT 题意:给你两棵同构的树,每个节点都有权值0/1,现在想改变第一棵树中部分点的权值,使得两棵树对应的节点权值相同,问最少改变多少节点. 题解:先考虑树hash+树形DP.树hash的方法同独钓寒江雪.设f[x][y]表示第一棵树中的x节点与第二棵树中的y节点对应时,

bzoj 3197 [Sdoi2013]assassin(Hash+DP+KM)

Description Input Output Sample Input 4 1 2 2 3 3 4 0 0 1 1 1 0 0 0 Sample Output 1 HINT [思路] Hash,DP,KM 树的形态可以有多种但是他的中心只有一个.先找出中心,如果在边上则新建一个节点.以中心为根建树,同时求出Hash.Hash函数如下: H[u]=((((A*H[son1])*p+H[son2])*p+H[son3])*p) 通过判断hash值和节点深度dep就可直到是否同构. 在树上进行DP

BZOJ 3197 Sdoi2013 assassin 动态规划+树同构+费用流

题目大意:给定一棵树和两组权值,求第一组权值最少改变多少个之后这棵树经过重标号之后与第二组权值相同 这个题做法很神- - 首先和3162一样的处理方式 我们先找到这棵树的重心作为根 如果重心有两个就新建一个根连向这两个重心 令f[x][y]表示x所在子树的第一组权值和y所在子树的第二组权值匹配的最小花销 转移的必要条件是x所在的子树与y所在的子树同构且x与y深度相同 为了保证无后效性,x的所有子节点与y的所有子节点之间的最小花销必须都已求出 那么我们将节点以深度的倒数为第一键值,Hash值为第二

YCB 的暑期计划

前言 YCB现在很弱(TAT) 暑假有一个月,赶快狂补一下. 大概的计划如下: 首先前期会以数据结构为主,毕竟代码能力太弱,涉及内容:线段树分治.二进制分组.KD-Tree. 等数据结构做到没有智商的时候加入一波数论,内容为 杜教筛.min_25筛. 然后中途小清新一下,做一些 组合博弈与构造题. 接着继续练代码能力,顺便学一些神奇的暴力:启发式合并.dsu on tree . 然后图论也忘的差不多了,就回过头去学点新东西,大概会有spfa判负环.0/1分数规划.差分约束. 估计这个时候也没有什

【bzoj3122】: [Sdoi2013]随机数生成器 数论-BSGS

[bzoj3122]: [Sdoi2013]随机数生成器 当a>=2 化简得 然后 BSGS 求解 其他的特判 : 当 x=t  n=1 当 a=1  当 a=0 判断b==t 1 /* http://www.cnblogs.com/karl07/ */ 2 #include <cstdlib> 3 #include <cstdio> 4 #include <cstring> 5 #include <cmath> 6 #include <map&

HDU 4415 Assassin’s Creed

实在难想,贪心.别人的思路:点击打开链接 Problem Description Ezio Auditore is a great master as an assassin. Now he has prowled in the enemies' base successfully. He finds that the only weapon he can use is his cuff sword and the sword has durability m. There are n enem

bzoj 3198: [Sdoi2013]spring 题解

[原题] 3198: [Sdoi2013]spring Time Limit: 40 Sec  Memory Limit: 256 MB Submit: 253  Solved: 95 Description Input Output Sample Input 3 3 1 2 3 4 5 6 1 2 3 0 0 0 0 0 0 4 5 6 Sample Output 2 HINT [题解]这道题明明是水题,坑了我两天!!!真是伤心.发现哈希都不熟练了. 首先很容易想到是2^6枚举01状态,使得1

BZOJ3130: [Sdoi2013]费用流[最大流 实数二分]

3130: [Sdoi2013]费用流 Time Limit: 10 Sec  Memory Limit: 128 MBSec  Special JudgeSubmit: 960  Solved: 505[Submit][Status][Discuss] Description Alice和Bob在图论课程上学习了最大流和最小费用最大流的相关知识.    最大流问题:给定一张有向图表示运输网络,一个源点S和一个汇点T,每条边都有最大流量.一个合法的网络流方案必须满足:(1)每条边的实际流量都不超

HDU 4415 Assassin&amp;#39;s Creed(贪心)

pid=4415">HDU 4415 题意: 壮哉我Assassin! E叔有一柄耐久度为m的袖剑,以及n个目标士兵要去解决. 每解决掉一个士兵,消耗袖剑Ai的耐久度.且获得该士兵的武器,能够使用该武器解决Bi名其它士兵. E叔要尽可能地消耗更少耐久度解决很多其它的敌人,求最小消耗与最大杀敌数. 思路: 我们把士兵分为两个集合:e1与e2.e1的士兵 Bi = 0 . e2 的 Bi > 0. 我们发现.假设能解决e2的随意一个,e2就可以全灭,那么我们对e2依据消耗进行升序排序,消