关闭

JSOI2016 独特的树叶 树的Hash判同构

标签: hashtree
1200人阅读 评论(3) 收藏 举报
分类:

题目大意

给你两棵树,一棵有N个节点的树A,另一棵有N+1个节点的树B,已知B树可以删去一个结点后变成A树。问删去的是哪个节点,如有多个合法的节点则输出在B树中编号最小的一个。

N105

解题思路

如果知道怎么判树的同构,那么这题就可以顺利解决,那么就先考虑一下怎么判树的同构。

树的同构:一个很显然的思路就是把每个节点当作根做一次树的Hash,判断两棵树是否同构时,只需把一颗树的所有Hash值丢到map中再对于另一棵树的每个Hash值看看map里有没有就可以了。

那怎么求树的Hash

对于一棵子树我们可以把直接相连的儿子的Hash值排个序,再类似字符串Hash的方法合并成一个状态值,最后把这个状态值Hash一下就可以求出当前子树的Hash值,可是如果暴力把一棵树所有的Hash值求出来是O(N2logN),因为要以每个点为根跑一遍。而只需要动态移根,每次更改状态就可以O(NlogN)求出一棵树的所有同构的Hash值。

回到本题,我们可以先求出与A树同构的所树的Hash值,对于B树我们能删的节点只能是入度为1的点,那么同样我们求出与树B同构的所有树,那么对于每个入度为1的点,我们求出删掉当前点是新的B树的Hash值,再看看A树的同构是否有相等即可。

程序

//YxuanwKeith
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <map>

using namespace std;
typedef unsigned long long LL;

const int MAXN = 1e5 + 5, MAXT = 1e7 + 5;
const LL Pri = 99877171;

struct HashList {LL Num; int New;} Map[MAXT];
struct Info {
    int Num, Poi;
    Info (void) {};
    Info (int A, int B) {Num = A, Poi = B;};
};

vector<Info> Son[MAXN];
vector<LL> Suf[MAXN];

LL State[MAXN], Pow[MAXN], Hash[MAXN];
int Cnt, N, Real[MAXN], Siz[MAXN], tot, Next[MAXN * 2], Go[MAXN * 2], Last[MAXN];
bool Exist[MAXT];

int Get(LL Sta) {
    int Now = Sta % MAXT;
    while (Map[Now].New != 0 && Map[Now].Num != Sta) Now = (Now + 1) % MAXT;
    if (!Map[Now].New) Map[Now].New = ++ Cnt, Map[Now].Num = Sta;
    return Map[Now].New;
}

void Link(int u, int v) {
    Next[++ tot] = Last[u], Last[u] = tot, Go[tot] = v;
}

bool Cmp(Info A, Info B) {return A.Num < B.Num;}

void Basis(int Now, int Pre) {
    for (int p = Last[Now]; p; p = Next[p]) {
        int v = Go[p];
        if (v == Pre) continue;
        Basis(v, Now);
        Son[Now].push_back(Info(Hash[v], v));
    }
    sort(Son[Now].begin(), Son[Now].end(), Cmp);
    int Size = Son[Now].size();
    LL Sta = 0;
    for (int i = 0; i < Size; i ++) 
        Sta = Sta * Pri + Son[Now][i].Num;
    Hash[Now] = Get(Sta);
}

void All(int Now, int Pre, LL PreS) {
    LL Sta = 0;
    if (Now != 1) Son[Now].push_back(Info(PreS, Pre));
    int Size = Son[Now].size();
    sort(Son[Now].begin(), Son[Now].end(), Cmp);
    for (int i = 0; i < Size; i ++) 
        Sta = Sta * Pri + Son[Now][i].Num;
    State[Now] = Sta, Siz[Now] = Size;
    Real[Now] = Get(Sta);
    Suf[Now].clear();
    Suf[Now].resize(Size + 1);
    for (int i = Size - 1, j = 0; i + 1; i --, j ++) 
        Suf[Now][i] = Suf[Now][i + 1] + Pow[j] * Son[Now][i].Num;
    LL Pref = 0;
    for (int i = 0; i < Size; i ++) {
        Info v = Son[Now][i];
        if (i) Pref = Pref * Pri + Son[Now][i - 1].Num;
        if (v.Poi == Pre) continue;
        int Num = Suf[Now][i + 1];
        All(v.Poi, Now, Get(Pref * Pow[Size - i - 1] + Suf[Now][i + 1]));
    }
}

void Solve(int Ord) {
    Basis(1, 0);
    All(1, 0, 0);
    if (Ord == 1) {
        for (int i = 1; i <= N; i ++) Exist[Real[i]] = 1;
        return;
    }
    if (!Next[Last[1]]) {
        int Now = Go[Last[1]];
        if (Exist[Get(State[Now] - Pow[Siz[Now] - 1])]) {
            printf("1\n");
            return;
        }
    }
    for (int i = 2; i <= N + 1; i ++) {
        if (Next[Last[i]]) continue;
        int Now = Go[Last[i]];
        if (!Exist[Get(State[Now] - Pow[Siz[Now] - 1])]) continue;
        printf("%d\n", i);
        return;
    }
}

void Prepare() {
    Pow[0] = 1;
    for (int i = 1; i <= N + 1; i ++) Pow[i] = Pow[i - 1] * Pri;
}

void Clear() {
    tot = 0;
    memset(Last, 0, sizeof Last);
    memset(Son, 0, sizeof Son);
    memset(Hash, 0, sizeof Hash);
}

int main() {
    scanf("%d", &N);
    Prepare();
    for (int i = 1; i < N; i ++) {
        int u, v;
        scanf("%d%d", &u, &v);
        Link(u, v), Link(v, u);
    }
    Solve(1);
    Clear();
    for (int i = 1; i <= N; i ++) {
        int u, v;
        scanf("%d%d", &u, &v);
        Link(u, v), Link(v, u);
    }
    Solve(2);
}
2
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:101467次
    • 积分:2710
    • 等级:
    • 排名:第13982名
    • 原创:143篇
    • 转载:4篇
    • 译文:0篇
    • 评论:136条
    最新评论