学习笔记——树上哈希

news2024/11/25 7:25:55

普通子树哈希

树上的很多东西都是转化成链上问题的,比如树上哈希
树上哈希,主要是用于树的同构这个东西上的
什么是树的同构?

如图,不考虑节点编号,三棵树是同构的

将树转化成链,一般有两种方式:环游欧拉序与欧拉序
为了尽可能减少哈希冲突,进制位越小越好
又因为不考虑节点编号,很明显,若是采用欧拉序的话,得要记录该节点孩子数
环游欧拉序只用进入打上1,出来打上2即可搞定
小tips:欧拉序相较于环游欧拉序可能更快,请量力而行

于是,就可以采用普通的哈希方式啦!

指定范围子树哈希

如果说是将子树横着割一刀呢?
如图,是一棵树

放心,就60个节点
我们考虑D节点的子树中,距离D不超过3的所有点
如图

接着是环游欧拉序(考虑在某些原因的份上,我只保留D的子树)

为什么我只写到10?因为作者实在太懒因为到10就够了
对于范围树上哈希,我们有两种方式——拼接与删除
因为哈希一般在取模的意义下,所以,删除是非常难以做到的~~(作者亲测过)~~
那只剩下拼接了,这个就和链上拼接一模一样了(也很像是前缀和)

模板题


题目主要考的是范围树上哈希

如果说看懂了前面的,这题就不难了。首先可以二分。因为若是 k k k是答案,那么一定存在两个节点的 k k k层子树是同构的。在其中任选两个对应的点,所组成的子树的子树一定是同构的
这么说显得很烦,翻译成人化就是:对于每个符合题目要求的 k k k层的两个子树(就是这两个字叔同构),他们的所有子树中一定有同构的,并且层数有 0 0 0、有 1 1 1、有 ⋯ \cdots 、有 k − 1 k-1 k1

于是,题目就这样转化成了求是否存在同构的 k k k层子树
我们可以对于每个节点,求出它的 k k k层祖先,代表这个节点绝对存在 k k k层子树;
再找出 k + 1 k+1 k+1曾祖先,代表这个节点的子树将要在他的祖先的子树中被删去(不被添加)
最后用一个map(建议使用gp_hash_table)统计答案
题目就这么结束了

代码

#pragma GCC optimize(1, "inline", "Ofast")
#pragma GCC optimize(2, "inline", "Ofast")
#pragma GCC optimize(3, "inline", "Ofast")
#include <bits/stdc++.h>
#include <bits/extc++.h>
using namespace std;
namespace IO {
class input {
private:
    bool isdigit(char c) { return ('0' <= c && c <= '9'); }

public:
    input operator>>(int &x) {
        x = 0;
        bool y = 1;
        char c = getchar();
        while (!isdigit(c)) y &= (c != '-'), c = getchar();
        while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
        if (!y)
            x = -x;
        return *this;
    }
    input operator>>(short &x) {
        x = 0;
        bool y = 1;
        char c = getchar();
        while (!isdigit(c)) y &= (c != '-'), c = getchar();
        while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
        if (!y)
            x = -x;
        return *this;
    }
    input operator>>(bool &x) {
        x = 0;
        bool y = 1;
        char c = getchar();
        while (!isdigit(c)) y &= (c != '-'), c = getchar();
        while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
        if (!y)
            x = -x;
        return *this;
    }
    input operator>>(long &x) {
        x = 0;
        bool y = 1;
        char c = getchar();
        while (!isdigit(c)) y &= (c != '-'), c = getchar();
        while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
        if (!y)
            x = -x;
        return *this;
    }
    input operator>>(long long &x) {
        x = 0;
        bool y = 1;
        char c = getchar();
        while (!isdigit(c)) y &= (c != '-'), c = getchar();
        while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
        if (!y)
            x = -x;
        return *this;
    }
    input operator>>(__int128 &x) {
        x = 0;
        bool y = 1;
        char c = getchar();
        while (!isdigit(c)) y &= (c != '-'), c = getchar();
        while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
        if (!y)
            x = -x;
        return *this;
    }
    input operator>>(unsigned int &x) {
        x = 0;
        bool y = 1;
        char c = getchar();
        while (!isdigit(c)) y &= (c != '-'), c = getchar();
        while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
        if (!y)
            x = -x;
        return *this;
    }
    input operator>>(unsigned short &x) {
        x = 0;
        bool y = 1;
        char c = getchar();
        while (!isdigit(c)) y &= (c != '-'), c = getchar();
        while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
        if (!y)
            x = -x;
        return *this;
    }
    input operator>>(unsigned long &x) {
        x = 0;
        bool y = 1;
        char c = getchar();
        while (!isdigit(c)) y &= (c != '-'), c = getchar();
        while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
        if (!y)
            x = -x;
        return *this;
    }
    input operator>>(unsigned long long &x) {
        x = 0;
        bool y = 1;
        char c = getchar();
        while (!isdigit(c)) y &= (c != '-'), c = getchar();
        while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
        if (!y)
            x = -x;
        return *this;
    }
    input operator>>(unsigned __int128 &x) {
        x = 0;
        bool y = 1;
        char c = getchar();
        while (!isdigit(c)) y &= (c != '-'), c = getchar();
        while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
        if (!y)
            x = -x;
        return *this;
    }
    input operator>>(double &x) {
        x = 0;
        bool y = 1;
        char c = getchar();
        while (!isdigit(c)) y &= (c != '-'), c = getchar();
        while (isdigit(c)) x = x * 10 + (c ^ 48), c = getchar();
        if (!y)
            x = -x;
        if (!isdigit(c))
            if (c != '.')
                return *this;
        double z = 1;
        while (isdigit(c)) z /= 10., x = x + z * (c ^ 48), getchar();
        return *this;
    }
    input operator>>(long double &x) {
        x = 0;
        bool y = 1;
        char c = getchar();
        while (!isdigit(c)) y &= (c != '-'), c = getchar();
        while (isdigit(c)) x = x * 10 + (c ^ 48), c = getchar();
        if (!y)
            x = -x;
        if (!isdigit(c))
            if (c != '.')
                return *this;
        double z = 1;
        while (isdigit(c)) z /= 10., x = x + z * (c ^ 48), c = getchar();
        return *this;
    }
    input operator>>(float &x) {
        x = 0;
        bool y = 1;
        char c = getchar();
        while (!isdigit(c)) y &= (c != '-'), c = getchar();
        while (isdigit(c)) x = x * 10 + (c ^ 48), c = getchar();
        if (!y)
            x = -x;
        if (!isdigit(c))
            if (c != '.')
                return *this;
        double z = 1;
        while (isdigit(c)) z /= 10., x = x + z * (c ^ 48), c = getchar();
        return *this;
    }
    input operator>>(std::string &x) {
        char c = getchar();
        x.clear();
        while (!(c != ' ' && c != '\n' && c != '	' && c != EOF && c)) c = getchar();
        while (c != ' ' && c != '\n' && c != '	' && c != EOF && c) {
            x.push_back(c);
            c = getchar();
        }
        return *this;
    }
    input operator>>(char *x) {
        char c = getchar();
        int cnt = 0;
        while (!(c != ' ' && c != '\n' && c != '	' && c != EOF && c)) c = getchar();
        while (c != ' ' && c != '\n' && c != '	' && c != EOF && c) {
            x[cnt++] = c;
            c = getchar();
        }
        return *this;
    }
    input operator>>(char x) {
        x = getchar();
        return *this;
    }
} pin;
};  // namespace IO
inline void wt(char ch) { putchar(ch); }
template <class T>
inline void wt(T x) {
    static char ch[40];
    int p = 0;
    if (x < 0)
        putchar('-'), x = -x;
    do
        ch[++p] = (x % 10) ^ 48, x /= 10;
    while (x);
    while (p) putchar(ch[p--]);
}
template <class T, class... U>
inline void wt(T x, U... t) {
    wt(x), wt(t...);
}
#define int unsigned long long
const int N = 1e5 + 7;
int n;
const int M = 2e5 + 7;
struct edge {
    int v, w, nxt;
} e[M];
int head[N], ct;
const int T = 19, K = 3;
int ll[N], x[M], nx[N];//x一定要开两倍!!!
int l[N], r[N];
int tp;
int getpw(int d) { return ll[d]; }
void addE(int u, int v, int w = 0) {
    e[++ct] = { v, w, head[u] };
    head[u] = ct;
}
void saddE(int u, int v, int w = 0) { addE(u, v, w), addE(v, u, w); }
int fa[N][T + 1];
__gnu_pbds::gp_hash_table<int, bool> cun;
void getx(int u = 1, int faa = 0) {
    l[u] = ++tp;
    x[tp] = (x[tp - 1] * K + 1);
    fa[u][0] = faa;
    for (int i = head[u]; i; i = e[i].nxt) {
        int v = e[i].v;
        getx(v, u);
    }
    r[u] = ++tp;
    x[tp] = (x[tp - 1] * K + 2);
}
int ytl[N];
typedef pair<int, int> pii;
vector<pii> vt[N];
bool chk(int mid) {
    memset(nx, 0, sizeof nx);
    cun.clear();
    memset(ytl, 0, sizeof ytl);
    for (int i = 1; i <= n; i++) vt[i].clear();
    for (int i = 1; i <= n; i++) {
        int tl = i;
        for (int j = T, k = mid; ~j; j--)
            if ((1ull << j) <= k)
                k -= (1ull << j), tl = fa[tl][j];
        if (tl == 0)
            continue;
        ytl[tl] = 1;
        tl = fa[tl][0];
        if (tl == 0)
            continue;
        // out<<i<<" "<<tl<<endl;
        vt[tl].push_back(pii(l[i], i));
    }
    for (int i = 1; i <= n; i++) sort(vt[i].begin(), vt[i].end());
    bool flg = 0;
    for (int i = 1; i <= n; i++) {
        if (!ytl[i])
            continue;
        int lr = l[i];
        for (auto j : vt[i]) {
            int k = j.second;
            //	cout<<k<<endl;
            (nx[i] *= getpw(l[k] - lr));
            (nx[i] += x[l[k] - 1] - (x[lr - 1] * getpw(l[k] - lr)));
            //	cout<<x[l[k]-1]<<" "<<x[lr-1]<<" "<<nx[i]<<endl;
            lr = r[k] + 1;
        }
        (nx[i] *= getpw(r[i] - lr + 1));
        (nx[i] += x[r[i]] - (x[lr - 1] * getpw(r[i] - lr + 1)));
        if (cun[nx[i]])
            return 1;
        // cout<<nx[i]<<endl;
        cun[nx[i]] = 1;
        //	cout<<nx[i]<<endl;
        //	puts("");
    }
    return flg;
}
main() {
    freopen("tree.in", "r", stdin);
    freopen("tree.out", "w", stdout);
    ll[0] = 1;
    for (int i = 1; i < N; i++) ll[i] = (ll[i - 1] * K);
    IO::pin >> n;
    for (int i = 1, x, y; i <= n; i++) {
        IO::pin >> x;
        while (x--) IO::pin >> y, addE(i, y);
    }
    getx();
    for (int j = 1; j <= T; j++)
        for (int i = 1; i <= n; i++) fa[i][j] = fa[fa[i][j - 1]][j - 1];
    int l = 0, r = n - 1;
    while (l < r) {
        int mid = l + r + 1 >> 1;
        // cout<<l<<" "<<r<<" "<<mid<<endl;
        if (chk(mid))
            l = mid;
        else
            r = mid - 1;
    }
    printf("%llu\n", l);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/994765.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

mongodb安装全过程详解

mongodb安装全过程详解 安装mongodb环境安装mongodb shell 安装mongodb环境 进入mongodb官网下载社区版服务器 点击跳转下载地址 选择适应自己系统的版本&#xff0c;然后下载 下图为下载后的文件&#xff0c;双击进行安装 需要注意的两个点 ①选择完整安装 ②不下载mongo…

C语言——指针进阶(二)

目录 七.函数指针数组 八.指向函数指针数组的指针 九.回调函数 结尾 七.函数指针数组 说白了函数指针数组就是函数指针名字后面添加[ ]&#xff0c;二者结合成为数组。 那么这个函数指针数组到底有什么用呢&#xff1f;先不用着急&#xff0c;下面用一个计算器功能代码为大…

【Linux】——网络基础:http协议

目录 前言 应用层 认识协议 协议的概念 传输结构化数据 序列化和反序列化 网络版本计算器 服务器端Server 客户端Client 协议定制 其它 运行效果 HTTP协议 HTTP的简介 认识URL urlencode和urldecode HTTP协议格式 HTTP请求 HTTP响应 HTTP的方法 GET和POST…

时序分解 | MATLAB实现基于LMD局部均值分解的信号分解分量可视化

时序分解 | MATLAB实现基于LMD局部均值分解的信号分解分量可视化 目录 时序分解 | MATLAB实现基于LMD局部均值分解的信号分解分量可视化效果一览基本介绍程序设计参考资料 效果一览 基本介绍 LMD局部均值分解 直接替换Excel即可运行包含频谱图相关系数图 Matlab语言 1.算法新颖…

UMA 2 - Unity Multipurpose Avatar☀️七.UMA API介绍

文章目录 🟥 UMA Data DNA参数引用位置🟥 UMA API介绍🟥 UMA Data DNA参数引用位置 我们想通过代码去控制如图所示参数,达到捏脸的目的.下面就是可以控制的代码: _dna["headSize"].Set(1); _avatar.BuildCharacter();我们观察发现操控代码类似Material去设置…

01_瑞萨GUI(LVGL)移植实战教程之软件和硬件准备

本系列教程配套出有视频教程&#xff0c;观看地址&#xff1a;https://www.bilibili.com/video/BV1gV4y1e7Sg 1. 软件和硬件准备 1.1 本节要点 本节学习如何搭建开发环境&#xff0c;硬件接线&#xff0c;为我们后续的学习做好基础必要的准备。 1.2资料准备 本文档所有用到…

flink 端到端一致性

背景 我们经常会混淆flink提供的状态一致性保证和数据端到端一致性保证的关系&#xff0c;总以为他们表达的是同一个意思&#xff0c;事实上&#xff0c;他们不是一个含义&#xff0c;flink只能保证其维护的内部状态的一致性&#xff0c;而数据端到端的一致性需要数据源&#…

Python下载、安装及如何配置Pycharm(Windows 11)详细教程

本文介绍在Windows 11系统Python的下载、安装及配置Pycharm 一、下载 官网地址&#xff1a;https://www.python.org/downloads/windows/ 1、选择Python 3.10.5版本下载 二、安装 1、选择自定义安装 2、全部默认勾选&#xff0c;点击Next 3、自定义安装路径 建议勾选inst…

il2cpp分析-gobal-metadata.dat解密

gobal-metadata.dat解密 工具:010Editor,IDA 7.5,Jadx,VS Code 样本Last Island of Survival_6.3_Apkpure.xapk 分析Il2cpp文件 打开ida,把libil2cpp拖到ida中按快捷键ShiftF12等待字符串分析完后,搜索global-metadata.dat 双击搜索出来的结果 点击aGlobalMetadata 然后…

【并发编程八股】进程、线程、并发编程三大特性

目录 进程与线程的概念&#xff1f;串行、并行、并发的概念&#xff1f;同步异步、阻塞非阻塞的概念&#xff1f;线程的创建的方式&#xff1f;继承 Thread 类&#xff0c;重写 run 方法实现Runnable接口&#xff0c;重写 run 方法实现Callable&#xff0c;重写 call 方法&…

蚂蚁链发布全新Web3品牌ZAN,涉及RWA、合规等服务

9月8日&#xff0c;在外滩大会见解论坛「从科幻到科技&#xff1a;Web3、元宇宙、AIGC」现场上&#xff0c;蚂蚁集团旗下的蚂蚁链联合Everest Ventures Group、HASHKEY、Morpheus labs发布全新Web3品牌ZAN。原蚂蚁链CTO张辉担任ZAN CEO。 该品牌致力于服务Web3机构客户与Web3应…

【数据结构】学习笔记

文章目录 绪论线性表栈、队列、数组串树与二叉树图查找排序 绪论 线性表 栈、队列、数组 串 树与二叉树 图 查找 排序

2023/9/9总结

nodejs Node.js 是基于chrome的v8引擎封装&#xff0c;独立执行于JavaScript代码的环境 Node.js环境中没有BOM和DOM 作用&#xff1a; 编写后端程序&#xff1a;提供数据和网页资源等 前端工程化&#xff1a;集成各种开发中使用的工具和技术 fs模块--读写文件 模块&#xf…

(LeetCode)两数相加深入分析Java版

两数相加&#xff08;题目如下&#xff09; 给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。 你可以假设除了数…

华为云云耀云服务器L实例评测 | 开启OPC UA之旅

OPC Unified Architecture (OPC UA)是一种用于工业自动化的M2M协议(Machine-to-machine)&#xff0c;具有平台独立性&#xff0c;在Windows和Linux上都可以运行。随着云服务在工业现场的不断普及&#xff0c;OPCUA服务也开始大量部署在云端。 本文以华为云云耀云服务器L为基础…

Mybatis中动态SQL标签和内置参数介绍

Mybatis中动态SQL标签和内置参数 一、MyBatis动态SQL 1.1、sql标签 sql标签用于抽取公用的SQL代码&#xff0c;定义sql标签的时候需要通过【id】属性设置唯一标识。 <?xml version"1.0" encoding"UTF-8" ?> <!DOCTYPE mapperPUBLIC "-…

CocosCreator3.8研究笔记(十二)CocosCreator 字体资源理解

Cocos Creator 常用的字体资源有三种&#xff1a;系统字体、动态字体、位图字体。 一、系统字体 系统字体是调用运行平台自带的系统字体来渲染文字&#xff0c;不需要用户在项目中添加任何相关资源。 使用系统字体&#xff0c; Label 组件 Use System Font 属性需要勾选。 Fo…

使用Spring Security保障你的Web应用安全

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

用户权限数据转换为用户组列表(3/3) - Excel PY公式

最近Excel圈里的大事情就是微软把PY塞进了Excel单元格&#xff0c;可以作为公式使用&#xff0c;轻松用PY做数据分析。系好安全带&#xff0c;老司机带你玩一把。 实例需求&#xff1a;如下是AD用户的列表,每个用户拥有该应用程序的只读或读写权限&#xff0c;现在需要创建新的…

迅软DSE文档权限管理:实现受限访问,确保重要机密绝不外泄

在现代办公中&#xff0c;文档已经成为信息存储、使用以及流转的重要载体&#xff0c;这些文档在企业内外共享和流转&#xff0c;期间若是谁都可以对其进行访问、复制、删除、截屏、打印……会给企业的信息安全带来许多隐患。 对文档实行差异化管理已经成为企业机密保护一部分&…