【洛谷5537】【XR-3】系统设计(哈希_线段树上二分)
我好像国赛以后就再也没有写过 OI 相关的博客 qwq
Upd: 这篇博客是 NOIP (现在叫 CSP 了)之前写的,但是咕到 CSP 以后快一个月才发表 …… 我最近这么咕怎么办啊 ……
题目
分析
这道题可以说是非常神了。这题看上去无从下手,但是 通过膜拜题解 后能发现一些美妙的性质。树是不修改的,任意一对祖先 - 后代之间的路径一定都可以表示成一个 固定的 整数序列。并且,如果 \(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】系统设计(哈希_线段树上二分)的更多相关文章
- [洛谷P1198/BZOJ1012][JSOI2008] 最大数 - 树状数组/线段树?
其实已经学了树状数组和线段树,然而懒得做题,所以至今没写多少博客 Description 现在请求你维护一个数列,要求提供以下两种操作: 1. 查询操作. 语法:Q L 功能:查询当前数列中末尾L个数 ...
- 「洛谷4197」「BZOJ3545」peak【线段树合并】
题目链接 [洛谷] [BZOJ]没有权限号嘤嘤嘤.题号:3545 题解 窝不会克鲁斯卡尔重构树怎么办??? 可以离线乱搞. 我们将所有的操作全都存下来. 为了解决小于等于\(x\)的操作,那么我们按照 ...
- 「洛谷3870」「TJOI2009」开关【线段树】
题目链接 [洛谷] 题解 来做一下水题来掩饰ZJOI2019考炸的心情QwQ. 很明显可以线段树. 维护两个值,\(Lazy\)懒标记表示当前区间是否需要翻转,\(s\)表示区间还有多少灯是亮着的. ...
- 分数规划模板(洛谷P4377 [USACO18OPEN]Talent Show)(分数规划,二分答案,背包)
分数规划是这样一个东西: 给定若干元素,每个元素有两个属性值\(a_i,b_i\),在满足题目要求的某些限制下选择若干元素并求出\(\frac{\sum a}{\sum b}\)的最大值. 如果没有限 ...
- 洛谷P2619 [国家集训队2]Tree I(带权二分,Kruscal,归并排序)
洛谷题目传送门 给一个比较有逼格的名词--WQS二分/带权二分/DP凸优化(当然这题不是DP). 用来解决一种特定类型的问题: 有\(n\)个物品,选择每一个都会有相应的权值,需要求出强制选\(nee ...
- bzoj 4592(洛谷 4344) [Shoi2015]脑洞治疗仪——线段树上二分
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4592 1操作就是用线段树来二分找到第一个有 k 个0的位置. 在洛谷上A了,与暴力和网上题解 ...
- 【洛谷4769】[NOI2018] 冒泡排序(动态规划_组合数学)
题目: 洛谷 4769 博客页面左下角的嘴嘴瓜封神之战中的题目 分析: 一个排列交换次数为 \(\frac{1}{2}\sum_{i=1}^{n}|i-p_i|\) 的充要条件是这个排列不存在长度为 ...
- POJ 1741.Tree and 洛谷 P4178 Tree-树分治(点分治,容斥版) +二分 模板题-区间点对最短距离<=K的点对数量
POJ 1741. Tree Time Limit: 1000MS Memory Limit: 30000K Total Submissions: 34141 Accepted: 11420 ...
- 洛谷P2462 [SDOI2007]游戏(哈希+最长路)
题面 传送门 题解 我们把字符的出现次数哈希起来,然后把每个点向能在它之后的点连边.那么这显然是一个\(DAG\),直接求最长路就行了 //minamoto #include<bits/stdc ...
随机推荐
- Arduino OV7670 live image over USB to PC
https://www.youtube.com/watch?v=L9DTW1ulsT0 https://www.youtube.com/watch?v=Dp3RMb0e1eA
- php面试题收藏
总结几个要素: 1.个人简介名字大写,内容需要详实,一是可以给人留下映像,二是减少不必要的与面试官交换个人信息的时间.准备一份好的口头自我介绍是很有必要的,毕竟准备一次能用很久,时间花在上面很实用,面 ...
- vim文本编辑器——文件导入、命令查找、导入命令执行结果、自定义快捷键、ab命令、快捷键的保存
1.文件的导入(r): 导入前: 导入后: 在光标处,将tmp目录下的zhbb文件的内容导入到了当前文件. 2.命令的查找: 3.导入命令的执行结果: 光标所在行为导入的位置. 4.自定义快捷键: ( ...
- .NET总结--泛型与泛型集合,应用场景
泛型优点 1.提高代码复用性,代码简洁直观 2.直接存储数据类型免去数据类型之间得隐式转换 3.免去拆箱装箱过程,提高效率 4.数据类型安全,存储时会验证是否对应该类型 泛型集合 一. ArrayLi ...
- (持续更新) C# 面试技术点、常见SQL技术点 和 解决高并发的相关技术
这篇博客 持续更新. 方便小伙伴们学习与面试前的复习
- 三个面向对象相关的装饰器@property@staticmathod@classmethod
@property 先看实例: from math import pi class Circle: def __init__(self,r): self.r = r @property def per ...
- [Beta]Scrum Meeting#1
github 本次会议项目由PM召开,时间为5月6日晚上10点30分 时长15分钟 任务表格 人员 昨日工作 下一步工作 木鬼 beta初步计划 撰写博客整理文档 swoip 前端改进计划 模块松耦合 ...
- 仅当使用了列列表并且 IDENTITY_INSERT 为 ON 时,才能为表'TableName'中的标识列指定显式值
当某表的一个列为自动增长列,是无法直接通过insert语句向给字段写入指定值,解决方法: SET IDENTITY_INSERT TABLE_NAME ON; INSERT INTO TABLE_NA ...
- MyBatis(九):Mybatis Java API批量操作(增、删、改、查)
最近工作中用到了mybatis的Java API方式进行开发,顺便也整理下该功能的用法,接下来会针对基本部分进行学习: 1)Java API处理一对多.多对一的用法: 2)增.删.改.查的用法: 3) ...
- A Survey of Visual Attention Mechanisms in Deep Learning
A Survey of Visual Attention Mechanisms in Deep Learning 2019-12-11 15:51:59 Source: Deep Learning o ...