我好像国赛以后就再也没有写过 OI 相关的博客 qwq

Upd: 这篇博客是 NOIP (现在叫 CSP 了)之前写的,但是咕到 CSP 以后快一个月才发表 …… 我最近这么咕怎么办啊 ……

题目

洛谷 5537

分析

这道题可以说是非常神了。这题看上去无从下手,但是 通过膜拜题解 后能发现一些美妙的性质。树是不修改的,任意一对祖先 - 后代之间的路径一定都可以表示成一个 固定的 整数序列。并且,如果 \(u,v,w\) 是祖先 - 后代且深度递增,则 \((u,v)\) 的序列与 \((v,w)\) 的序列拼接起来就是 \((u,w)\) 的序列。

如图,\((1,2)\) 之间的序列是 "1" (因为 \(2\) 是 \(1\) 的第一小的儿子);\((1,5)\) 之间的序列是 "12" (因为从 \(1\) 到 \(5\) 要先走第一小,再走第二小),恰好是 \((1,2)\) 和 \((2,5)\) 的序列拼起来。

这样,我们就可以用一个序列(对应着从根到这个结点的走法)来表示一个结点了(设 \(u\) 的序列为 \(s_u\) )。从结点 \(u\) 开始按照一个序列 \(a\) 走,走到的结点(如果存在)对应的序列就是 \(s_u\) 和 \(a\) 拼接起来(相当于从根开始先按照 \(s_u\) 走到 \(u\) ,然后按 \(a\) 走到 \(s_u+a\) )。把序列 - 结点的映射关系用哈希表存下来,就可以 \(O(1)\) 判断从一个结点按照一个序列走若干步能否走到一个结点,以及查询如果能走到,走到的是哪个结点。

那么我们就有了一个做法:预处理出每个结点的序列的哈希值,并用线段树维护给定序列的哈希值。查询时二分走的长度(即取序列 \([l,\mathrm{mid}]\) 的部分),判断能否走到一个结点。直接二分是 \(O(nlog^2n)\) 过不去,必须在线段树上二分。

代码

比较弱,写了 6KB 。顺便说下陕西省美术馆门口写代码体验极佳。

注意拼接两个哈希值时乘的种子的次数。(这是个大坑)

#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <vector>
using namespace std; namespace zyt
{
template<typename T>
inline bool read(T &x)
{
char c;
bool f = false;
x = 0;
do
c = getchar();
while (c != EOF && c != '-' && !isdigit(c));
if (c == EOF)
return false;
if (c == '-')
f = true, c = getchar();
do
x = x * 10 + c - '0', c = getchar();
while (isdigit(c));
if (f)
x = -x;
return true;
}
template<typename T>
inline void write(T x)
{
static char buf[20];
char *pos = buf;
if (x < 0)
putchar('-'), x = -x;
do
*pos++ = x % 10 + '0';
while (x /= 10);
while (pos > buf)
putchar(*--pos);
}
const int N = 5e5 + 10;
typedef long long ll;
int rot, n, m, q, arr[N];
vector<int> g[N];
namespace Hash
{
typedef pair<int, int> pii;
typedef pii hash_t;
const pii seed = pii(71, 853), P = pii(int(1e9 + 7), int(1e9 + 9));
hash_t pow[N];
pii operator + (const pii &a, const pii &b)
{
return pii((a.first + b.first) % P.first, (a.second + b.second) % P.second);
}
pii operator * (const pii &a, const pii &b)
{
return pii(int((ll)a.first * b.first % P.first), int((ll)a.second * b.second % P.second));
}
void init()
{
pow[0] = pii(1, 1);
for (int i = 1; i < N; i++)
pow[i] = pow[i - 1] * seed;
}
}
using namespace Hash;
hash_t h[N];
namespace Hash_Table
{
const int P = 1e6 - 17, seed = 24601;
struct edge
{
pii to;
int w, next;
}e[N];
int head[P], ecnt;
void add(const int a, const pii b, const int c)
{
e[ecnt] = (edge){b, c, head[a]}, head[a] = ecnt++;
}
void init()
{
ecnt = 0;
memset(head, -1, sizeof(head));
}
void insert(const pii a, const int b)
{
int tmp = ((ll)a.first * seed + a.second) % P;
add(tmp, a, b);
}
int query(const pii a)
{
int tmp = ((ll)a.first * seed + a.second) % P;
for (int i = head[tmp]; ~i; i = e[i].next)
if (e[i].to == a)
return e[i].w;
return -1;
}
}
namespace Segment_Tree
{
struct node
{
hash_t h;
}tree[N << 2];
void update(const int rot, const int len)
{
tree[rot].h = tree[rot << 1].h * pow[len] + tree[rot << 1 | 1].h;
}
void build(const int rot, const int lt, const int rt)
{
if (lt == rt)
return void(tree[rot].h = hash_t(arr[lt], arr[lt]));
int mid = (lt + rt) >> 1;
build(rot << 1, lt, mid), build(rot << 1 | 1, mid + 1, rt);
update(rot, rt - mid);
}
void change(const int rot, const int lt, const int rt, const int pos, const int x)
{
if (lt == rt)
return void(tree[rot].h = hash_t(x, x));
int mid = (lt + rt) >> 1;
if (pos <= mid)
change(rot << 1, lt, mid, pos, x);
else
change(rot << 1 | 1, mid + 1, rt, pos, x);
update(rot, rt - mid);
}
hash_t query(const int rot, const int lt, const int rt, const int ls, const int rs)
{
if (ls <= lt && rt <= rs)
return tree[rot].h;
int mid = (lt + rt) >> 1;
if (rs <= mid)
return query(rot << 1, lt, mid, ls, rs);
else if (ls > mid)
return query(rot << 1 | 1, mid + 1, rt, ls, rs);
else
return query(rot << 1, lt, mid, ls, rs) * pow[min(rs, rt) - mid]
+ query(rot << 1 | 1, mid + 1, rt, ls, rs);
}
int query(const int rot, const int lt, const int rt, const int ls, const int rs, const hash_t h)
{
if (lt == rt)
return Hash_Table::query(h * seed + tree[rot].h);
int mid = (lt + rt) >> 1;
if (rs <= mid)
return query(rot << 1, lt, mid, ls, rs, h);
else if (ls > mid)
return query(rot << 1 | 1, mid + 1, rt, ls, rs, h);
else
{
int w;
hash_t hh = h * pow[mid - max(ls, lt) + 1] +
(ls <= lt ? tree[rot << 1].h : query(1, 1, m, ls, mid));
if ((w = Hash_Table::query(hh)) != -1)
{
int tmp = query(rot << 1 | 1, mid + 1, rt, ls, rs, hh);
if (tmp == -1)
return w;
else
return tmp;
}
else
return query(rot << 1, lt, mid, ls, rs, h);
}
}
}
void dfs(const int u)
{
sort(g[u].begin(), g[u].end());
int cnt = 0;
for (vector<int>::iterator it = g[u].begin(); it != g[u].end(); it++)
{
int v = *it;
++cnt;
h[v] = h[u] * seed + hash_t(cnt, cnt);
dfs(v);
}
}
int work()
{
using namespace Segment_Tree;
init();
Hash_Table::init();
read(n), read(m), read(q);
for (int i = 1; i <= n; i++)
{
int a;
read(a);
if (!a)
rot = i;
else
g[a].push_back(i);
}
h[rot] = hash_t(0, 0);
dfs(rot);
for (int i = 1; i <= n; i++)
Hash_Table::insert(h[i], i);
for (int i = 1; i <= m; i++)
read(arr[i]);
build(1, 1, m);
while (q--)
{
int opt;
read(opt);
if (opt == 1)
{
int x, l, r, ans;
read(x), read(l), read(r);
ans = query(1, 1, m, l, r, h[x]);
write(ans == -1 ? x : ans), putchar('\n');
}
else
{
int a, b;
read(a), read(b);
change(1, 1, m, a, b);
} }
return 0;
}
}
int main()
{
freopen("5537.in", "r", stdin);
return zyt::work();
}

【洛谷5537】【XR-3】系统设计(哈希_线段树上二分)的更多相关文章

  1. [洛谷P1198/BZOJ1012][JSOI2008] 最大数 - 树状数组/线段树?

    其实已经学了树状数组和线段树,然而懒得做题,所以至今没写多少博客 Description 现在请求你维护一个数列,要求提供以下两种操作: 1. 查询操作. 语法:Q L 功能:查询当前数列中末尾L个数 ...

  2. 「洛谷4197」「BZOJ3545」peak【线段树合并】

    题目链接 [洛谷] [BZOJ]没有权限号嘤嘤嘤.题号:3545 题解 窝不会克鲁斯卡尔重构树怎么办??? 可以离线乱搞. 我们将所有的操作全都存下来. 为了解决小于等于\(x\)的操作,那么我们按照 ...

  3. 「洛谷3870」「TJOI2009」开关【线段树】

    题目链接 [洛谷] 题解 来做一下水题来掩饰ZJOI2019考炸的心情QwQ. 很明显可以线段树. 维护两个值,\(Lazy\)懒标记表示当前区间是否需要翻转,\(s\)表示区间还有多少灯是亮着的. ...

  4. 分数规划模板(洛谷P4377 [USACO18OPEN]Talent Show)(分数规划,二分答案,背包)

    分数规划是这样一个东西: 给定若干元素,每个元素有两个属性值\(a_i,b_i\),在满足题目要求的某些限制下选择若干元素并求出\(\frac{\sum a}{\sum b}\)的最大值. 如果没有限 ...

  5. 洛谷P2619 [国家集训队2]Tree I(带权二分,Kruscal,归并排序)

    洛谷题目传送门 给一个比较有逼格的名词--WQS二分/带权二分/DP凸优化(当然这题不是DP). 用来解决一种特定类型的问题: 有\(n\)个物品,选择每一个都会有相应的权值,需要求出强制选\(nee ...

  6. bzoj 4592(洛谷 4344) [Shoi2015]脑洞治疗仪——线段树上二分

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4592 1操作就是用线段树来二分找到第一个有 k 个0的位置. 在洛谷上A了,与暴力和网上题解 ...

  7. 【洛谷4769】[NOI2018] 冒泡排序(动态规划_组合数学)

    题目: 洛谷 4769 博客页面左下角的嘴嘴瓜封神之战中的题目 分析: 一个排列交换次数为 \(\frac{1}{2}\sum_{i=1}^{n}|i-p_i|\) 的充要条件是这个排列不存在长度为 ...

  8. POJ 1741.Tree and 洛谷 P4178 Tree-树分治(点分治,容斥版) +二分 模板题-区间点对最短距离<=K的点对数量

    POJ 1741. Tree Time Limit: 1000MS   Memory Limit: 30000K Total Submissions: 34141   Accepted: 11420 ...

  9. 洛谷P2462 [SDOI2007]游戏(哈希+最长路)

    题面 传送门 题解 我们把字符的出现次数哈希起来,然后把每个点向能在它之后的点连边.那么这显然是一个\(DAG\),直接求最长路就行了 //minamoto #include<bits/stdc ...

随机推荐

  1. centos 7 开启外网方法端口方法

    firewall-cmd --zone=public --add-port=80/tcp --permanent  开启80端口 systemctl stop firewalld.service sy ...

  2. There is insufficient memory for the Java Runtime Environment to continue.

    There is insufficient memory for the Java Runtime Environment to continue.

  3. A@[G!C]%008

    A@[G!C]%008 A Simple Calculator 细节题. B Contiguous Repainting 最后只要有连续\(K\)个鸽子同色就可以构造方案,枚举+前缀和 C Tetro ...

  4. Python中的异步任务队列 arq

    引言 最近在用 sanic 写东西,所有涉及到IO阻塞的代码都需要用 aio 的模块,好在近年来 asyncio 生态圈发展的还算不错,该有的都有 ~ 近期业务中 登录/注册 业务涉及的很复杂(涉及到 ...

  5. luogu P3327 [SDOI2015]约数个数和 莫比乌斯反演

    题面 我的做法基于以下两个公式: \[[n=1]=\sum_{d|n}\mu(d)\] \[\sigma_0(i*j)=\sum_{x|i}\sum_{y|j}[gcd(x,y)=1]\] 其中\(\ ...

  6. JQuery的Ajax标准写法

    Ajax的标准写法 $.ajax({ url:"http://www.xxx",//请求的url地址 dataType:"json",//返回的格式为json ...

  7. SpringBoot——Profile多环境支持

    1.多profile文件形式 主配置文件编写时, 文件名可以是application-{profile}.properties/yml 默认使用的application.properties的配置. ...

  8. Win10登陆界面卡住,进去后无法打开网络相关的设置,谷歌浏览器无法上网

    今天Win10抽风,进入登录页面输入用户名和密码之后,大约过了10分钟才进入桌面.重启后仍然如此. 经过调查,问题主要出在网络相关模块上,网络无法正常初始化,导致登录一直卡在网络初始化上. 解决方法如 ...

  9. 异常值检测方法(Z-score,DBSCAN,孤立森林)

     机器学习_深度学习_入门经典(博主永久免费教学视频系列) https://study.163.com/course/courseMain.htm?courseId=1006390023&sh ...

  10. Java基础 awt Button 鼠标放在按钮上背景颜色改变,鼠标离开背景颜色恢复

        JDK :OpenJDK-11      OS :CentOS 7.6.1810      IDE :Eclipse 2019‑03 typesetting :Markdown   code ...