惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

P
Proofpoint News Feed
C
CERT Recently Published Vulnerability Notes
O
OpenAI News
V
Vulnerabilities – Threatpost
C
Cybersecurity and Infrastructure Security Agency CISA
S
Schneier on Security
Latest news
Latest news
F
Full Disclosure
T
Tenable Blog
T
Troy Hunt's Blog
The Last Watchdog
The Last Watchdog
S
Secure Thoughts
L
LangChain Blog
有赞技术团队
有赞技术团队
Project Zero
Project Zero
Cloudbric
Cloudbric
爱范儿
爱范儿
GbyAI
GbyAI
C
CXSECURITY Database RSS Feed - CXSecurity.com
T
The Exploit Database - CXSecurity.com
S
Security @ Cisco Blogs
Hugging Face - Blog
Hugging Face - Blog
Recorded Future
Recorded Future
大猫的无限游戏
大猫的无限游戏
Last Week in AI
Last Week in AI
C
Cisco Blogs
WordPress大学
WordPress大学
Apple Machine Learning Research
Apple Machine Learning Research
小众软件
小众软件
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
V2EX - 技术
V2EX - 技术
Engineering at Meta
Engineering at Meta
Spread Privacy
Spread Privacy
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Hacker News: Ask HN
Hacker News: Ask HN
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Schneier on Security
Schneier on Security
T
Threat Research - Cisco Blogs
M
MIT News - Artificial intelligence
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
K
Kaspersky official blog
The Hacker News
The Hacker News
V
V2EX
F
Fortinet All Blogs
L
LINUX DO - 最新话题
Cisco Talos Blog
Cisco Talos Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
N
News | PayPal Newsroom
博客园 - 三生石上(FineUI控件)
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org

博客园_首页

Linux实操--组管理、权限管理和定时任务 Java + EasyExcel 实现单个接口导出多个Excel Mem0 源码解析系列(二):提示词工程的深度剖析 Openclaw TaskFlow究竟是什么?和普通Skill技能有什么区别 博文阅读密码验证 - 博客园 嘉立创开源:应该是全网MicroPython教程最多的开发板 Hermes Agent 集成实践:从协议到生产 2026年AI编程工具横评:Cursor、Codex、Claude Code、Zed、Windsurf Java程序员必看的RAG入门教程 2026 AI效率神器:Superpowers + Claude Code 保姆级教程 本地大模型部署全攻略:从 0 到 1 玩转 Ollama 【从0到1构建一个ClaudeAgent】内存管理-上下文压缩 .NET 高级开发 | 设计、实现一个事件总线框架 电子小白入门之NE555 3. WorkBuddy:隐藏玩法,一键召唤专家,让 AI 以"专家身份"给你干活 和AI一起搞事情#3:Claude Teammate 游戏开发翻车实录 【OpenClaw】通过 Nanobot 源码学习架构---(7)Memory C# .NET 周刊|2026年3月3期 我在 Debian 11 上把 K8s 单机搭起来了,过程没你想的那么顺(/opt 目录版) 深度学习进阶(七)Data-efficient Image Transformer CLI+Skill搭建浏览器AI自动化框架,告别一切重复枯燥任务 告别Token账单无底洞:OpenClaw本地部署,重塑企业数据主权的唯一解 FastAPI+Vue:文件分片上传+秒传+断点续传,这坑我帮你踩平了! SBTI 爆火后,我做了个程序员版的 CBTI。。已开源 + 附开发过程 多模态检索开始进入工程期:用 Sentence Transformers 搭建可落地的 Multimodal RAG 100多行代码实现一个最简单的Agent(用ReAct) Claude Code 通关手册(八):推荐 5 个 Hooks,代码质量提升 3 倍 老板:“有人截图了!”。安全部门:“收到,马上查暗水印!” - why技术 技术之外,皆是人间 C#/.NET/.NET Core技术前沿周刊 | 第 69 期(2026年4.01-4.12) Snack JSONPath 项目架构分析 Claude Code Buddy 小析:一个非核心功能,如何体现产品的细节完成度 AI新时代下的图床管理方案-Cloudflare图床+MCP+Skills方案指南 化繁为简:顺丰速运App如何通过 HarmonyOS SDK实现专业级空间测量 从零实现富文本编辑器#13-React非编辑节点的内容渲染 AI开发-python-langchain框架(3-23-OpenAI Functions风格Tool Calling智能助手) .NET + AI 进阶实战:基于类的技能开发 - 打造可治理的 Agent 能力模块 【从0到1构建一个ClaudeAgent】规划与协调-技能 上周热点回顾(4.6-4.12) 电子小白的工具三件套:面包板、杜邦线、万能板 单表五亿数据的查询优化 | Mysql、StarRocks 2. WorkBuddy:从“我是谁”到“帮我干活” C# 如何减少代码运行时间:7 个实战技巧 基于HelixToolkit.SharpDX 渲染3D模型 - 笺上知微 从零开始的双臂具身VLA起源及现阶段发展综述 - SkyXZ 记对 xonsh shell 的使用, 脚本编写, 迁移及调优 - pluvium27 受够了Vibe Coding的失控?换个起点,让AI事半功倍 从开始配置漏洞环境到漏洞复现流程 - 難しい 关于10年工作经验的程序员对OpenClaw的实战经验分享以及看法 - 虚无境 Any metadata 的内存布局 C# .NET 周刊|2026年3月2期 - InCerry 我帮你测过了,测试圈排名第二的 Skill 依然很牛逼 Skill Discovery | 无监督技能发现的经典工作总结 - MoonOut PbootCMS 网站内容数量多导致访问慢?这些实用优化方案帮你提速! - 家兴网络技术工作室 上下文工程是什么?过时了么?一文讲明白! - 一枫说码 网站漏洞怎么发现并修复?一篇实用指南(附完整流程) - 家兴网络技术工作室 开了 TUN 模式还是直连?90% 的人都踩过这个坑 Github日报|2026年04月12日 - AI一族 AScript扩展多种脚本语言 - rockey627 AI 学习笔记:Agent 的记忆机制 你能被装进一个文件里吗?——7 万人把同事"蒸馏"成了 AI - 我没有三颗心脏 Claude Code 通关手册(七):给 AI 装上技能包——Skills 完全指南 - 暮色之狐 在浏览器中快速编辑代码:VSCode Web 集成实践 - Newbe36524 蒸馏自己 skill?基于 Deepseek 的蒸馏器,丐版蒸馏方式,简单便捷 - To_Carpe_Diem Spring AI Aliababa和AgentScope,哪个更好? - 苏三说技术 Etsy 把 1000 个 MySQL 分片迁进 Vitess:425TB 数据背后的真正问题不是性能,而是运维规模 MicroPython LVGL基础知识和概念:底层渲染与性能优化 - FreakStudio 数据库草图算法 Python 潮流周刊#146:CPython 引入 Rust 的进展 - 豌豆花下猫 最小生成树 - mofei1116 红日靶场七:从外网入口、容器逃逸到 AD 接管的完整利用链复盘 - YouDiscovered1t 分享四款开源且实用的 Kafka 管理工具 - 追逐时光者 vLLM 权重加载机制全解析:从挑战到理想架构 LCT 学习笔记 - ACehomoxue Avalonia UI 12.0.0 正式发布:架构演进和性能飞跃 - 张善友 当 AI Agent 把调用链拉长,延迟开始成为一门生意 conhost.exe 无法显示 U+2717 - 145a 太秀了,我把自己蒸馏成了 Skill!已开源 - 程序员鱼皮 ASP.NET Core 内存缓存实战:一篇搞懂该怎么配、怎么避坑 基于 Ghostty 带有分割标签页和为 Claude 编程设计的通知终端 - BugShare AI 焊死入口:教育的“操作系统级”重塑 - 郝hai 初级Java开发工程师使用sql脚本编写代码的过程是简单而且不糊涂 - CoderOilStation Claude Code通关手册(六):MCP协议完全指南 - 暮色之狐 边框灯光环绕动画特效实现指南 - Newbe36524 开源:子木蒸馏版的 SEO 审计工具 seo-audit-skill v1.0 我所理解的Python元模型 【从0到1构建一个ClaudeAgent】规划与协调-TodoWrite - 程序员Seven Claude 和 Codex 在审计 Skill 上性能差异探究 - ACai_sec AScript如何实现中文脚本引擎 - rockey627 【渗透测试】HTB Season10 Garfield 全过程wp - dynasty_chenzi Android 开发者为什么必须掌握 AI 能力?端侧视角下的技术变革 树状数组正确性证明 - AC-wyr 你的 AI 焦虑,可能比 AI 本身更危险——ATM 机没有消灭银行柜员,但恐慌消灭了你的判断力 - 我没有三颗心脏 一个拉胯的分库分表方案有多绝望?整个部门都在救火! - 冰河团队 动态规划入门必学之走方格问题 - Ofnoname PostgREST 与 PostgreSQL 角色权限配置全解析(生产级实践) - SheepDog1998 使用 UEFI 图形输出协议 GOP 在屏幕上显示图像的方法 - 阿源- Claude Code通关手册(五):组建你的AI专家团队,子代理系统 - 暮色之狐 一个程序员到架构师的催婚路之感悟(整整10年后的催婚相亲感悟) - MisterLip 用 Agent Skill 自动生成工作周报 - 赵康
可持久化线段树/主席树 学习笔记
medal_dreams · 2026-04-18 · via 博客园_首页

定义

我们引用一下 OIWIKI 的定义:

可持久化数据结构 (Persistent data structure) 总是可以保留每一个历史版本,并且支持操作的不可变特性 (immutable). --OIWIKI

故可持久化线段树就是可以存储多个历史版本,并且进行一些操作的线段树。

用途

可持久化线段树是很多可持久化的起点,它允许查询,修改某一版本的某一值,或者查询静态区间第 \(k\) 小。

值得一提的是,允许访问或修改所有版本的可持久化叫做完全可持久化。
详见:可持久化数据结构简介-OIWIKI

基础实现

对于多个版本,我们很明显可以对于每一个版本建一个线段树,但是这样,我们的空间复杂度会爆炸!

故我们考虑让前面的版本对当前版本有一定贡献,考虑对于每一个版本,有以下两种情况:

  1. 如果没有修改的,我们就让指针指向上一个版本的这个未修改节点。
  2. 如果有修改的,我们就对其新建一个节点。
    如图:
    主席树

其实现依赖于用结构体维护 \(ls, rs\),并且对于一个新节点,我们先让其等于原节点,并且给其一个新的编号,再依次更新下面的节点。

代码如下:

struct node{
	int val, ls, rs;
}nd[N << 7];
#define ls(k) nd[k].ls
#define rs(k) nd[k].rs
int newnode(int p){nd[++ tot] = nd[p];return tot;}

修改操作

可见,因为我们将一个节点赋了一个新的编号,所以我们需要让这个节点变为这个编号,其实现方法即为在 update 操作中将引入 \(p\) 编号的节点加上址传递,即加上取地址符 &。其他和普通线段树没有什么很大的差异。

代码如下:

void update(int &p, int l, int r, int x, int val)
{
	p = newnode(p);//变为新编号
	if(l == r){nd[p].val = val;return ;}//更新值
	int mid = l + r >> 1;
	if(x <= mid) update(ls(p), l, mid, x, val);
	else update(rs(p), mid + 1, r, x, val);
	//pushup(p);
     //pushup 操作看题,可有可无
}

查询操作

因为所有版本我们已经处理好了,直接查询即可。

代码如下:

ll query(int p, int l, int r, int x)
{
	if(l == r) return nd[p].val;
	int mid = l + r >> 1;
	if(x <= mid) return query(ls(p), l, mid, x);
	else return query(rs(p), mid + 1, r, x);
}

基础实现例题

1.P3919 【模板】可持久化线段树 1(可持久化数组)

简要题意:

维护一个长度为 \(n\) 的数组 \(a\),进行 \(m\) 次以下操作:

  1. 形如 v 1 p c 表示把版本 \(v\)\(a_p\) 改为 \(c\)
  2. 形如 v 2 p 输出版本 \(v\)\(a_p\)

对于每个操作,都会生成一个对应的版本。

其中 \(1 \le n,m \le 10 ^ 6\)\(1 \le p \le n\)\(-10^9 \le a_i,c \le 10^9\)
如果当前是第 \(x\) 次操作,则 \(0 \le v < x\)
版本 \(0\) 为原始数组。

思路

按照上面代码和思想,模拟即可。
代码:

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
constexpr int N = 1e6 + 66;
int root[N], tot;
struct Seg_Ment_Tree{
	struct node{
		int val, ls, rs;
	}nd[N << 7];
    #define ls(k) nd[k].ls
    #define rs(k) nd[k].rs
	int newnode(int p){nd[++ tot] = nd[p];return tot;}
	void update(int &p, int l, int r, int x, int val)
	{
		p = newnode(p);
		if(l == r){nd[p].val = val;return ;}
		int mid = l + r >> 1;
		if(x <= mid) update(ls(p), l, mid, x, val);
		else update(rs(p), mid + 1, r, x, val);
	}
	ll query(int p, int l, int r, int x)
	{
		if(l == r) return nd[p].val;
		int mid = l + r >> 1;
		if(x <= mid) return query(ls(p), l, mid, x);
		else return query(rs(p), mid + 1, r, x);
	}
}seg;
int n, m, a;
int main()
{
	ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);
	cin >> n >> m;
	for(int i = 1;i <= n;i ++) cin >> a, seg.update(root[0], 1, n, i, a);//初始化初始版本(即原数组)
	for(int i = 1;i <= m;i ++)
	{
		int od, v, p, c;
		cin >> v >> od >> p;
		root[i] = root[v];//当前版本为给定版本
		if(od == 1)
		{
			cin >> c;
			seg.update(root[i], 1, n, p, c);//修改
		}
		else cout << seg.query(root[i], 1, n, p) << '\n';
	}
	return 0;
}

2.P1383 高级打字机

简要题意:

开始有一空串,要进行 \(n\) 次以下操作:

  1. 形如 T x,表示在后面加入一字符 \(x\)
  2. 形如 U x,表示撤销 \(x\) 次除操作三的操作。
  3. 形如 Q x 表示查询当前字符串的第 \(x\) 个字符。

\(n \le 10^5\)

思路

板子,按照模板 1 查询即可,注意输入的字符还是数字,不然会 WA,还有注意分辨一下操作二和操作一都属于修改操作,所以需要新建一个版本,具体看代码。

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
constexpr int N = 1e5 + 66;
int root[N], tot;
struct Seg_Ment_Tree{
	struct node{
		int val, ls, rs, su;
	}nd[N << 7];
#define ls(k) nd[k].ls
#define rs(k) nd[k].rs
	int newnode(int p){nd[++ tot] = nd[p];return tot;}
	void pushup(int p){nd[p].su = nd[ls(p)].su + nd[rs(p)].su;}//维护字符个数
	void update(int &p, int l, int r, int x, int val)
	{
		p = newnode(p);
		if(l == r){nd[p].val = val;nd[p].su = 1;return ;}
		int mid = l + r >> 1;
		if(x <= mid) update(ls(p), l, mid, x, val);
		else update(rs(p), mid + 1, r, x, val);
		pushup(p);
	}
	ll query(int p, int l, int r, int x)
	{
		if(l == r) return nd[p].val;
		int mid = l + r >> 1;
		if(x <= mid) return query(ls(p), l, mid, x);
		else return query(rs(p), mid + 1, r, x);
	}
}seg;
int n, m, a, cnt;
int main()
{
	ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);
	cin >> n;
	for(int i = 1;i <= n;i ++)
	{
		char c;cin >> c;
		if(c == 'T')
		{
			char s;cin >> s;
			cnt ++;//新建版本
			root[cnt] = root[cnt - 1];//等于上一个版本
			seg.update(root[cnt], 1, 1e5, seg.nd[root[cnt]].su + 1, s);//在上一个版本的后面加入一个字符
		}
		else if(c == 'U')
		{
			int s;cin >> s;
			cnt ++;//新建版本
			root[cnt] = root[cnt - 1 - s];//等于 cnt - s 的,但是我们 ++ 了,所以让 cnt - 1
		}
		else 
		{
			int s;cin >> s;
			cout << char(seg.query(root[cnt], 1, 1e5, s)) << '\n';//直接查询即可
		}
	}
	return 0;
}

进阶一点的实现

现在我们讨论静态区间第 \(k\) 小。

先引入一下例题吧:

P3834 【模板】可持久化线段树 2

简要题意:
给定一个长度为 \(n\) 的序列 \(a\),询问 \(m\)\(a\)\([l,r]\) 区间第 \(k\) 小的数的值。
\(1 \le n,m \le 2 \times 10 ^ 5\)\(0 \le a_i \le 10^9\)\(1 \le l, r \le n\)\(1 \le k \le r - l + 1\)

可见,我们很难在原序列中,用原本思想直接在 \(i\) 修改,因为我们第 \(k\) 小是关系大小的。
那大小我们怎么处理呢?考虑使用权值线段树来处理,这样我们就可以处理大小关系。
那太大了怎么办?可持久化线段树是自带动态开点性质的,我们只需关注题目的空间大小是不是过小了(比如 \(125MB\)),如果太小了,就离散化一下即可。

修改

对于每一个位置,我们考虑建立一个新版本,并在新版本所维护的权值线段树上插入它的值。
所以修改代码其实是没什么区别的,但是 \(a_i\) 可能重复,所以不能直接赋值。

考虑我们还需要维护什么,以及我们需不需要 pushup。显然的是我们需要查询前面区间的数的个数,所以我们需要 pushup 来让父节点维护区间。

代码如下:

void pushup(int p){nd[p].val = nd[ls(p)].val + nd[rs(p)].val;}
void update(int &p, int l, int r, int x, int val)
{
	p = newnode(p);
	if(l == r){nd[p].val += val;return ;}
	int mid = l + r >> 1;
	if(x <= mid) update(ls(p), l, mid, x, val);
	else update(rs(p), mid + 1, r, x, val);
	pushup(p);
}

查询

但是我们怎么查询的?我们考虑,对于 \([l,r]\) 我们现在已经有了对应的版本,那一个 \([1,l]\)\([1,r]\) 内有的数的个数的差不就是 \([l,r]\) 中数的个数减去一吗?考虑到这个也可以推广到其他区间,所以我们在查询时传入两个节点,每一次判断前面的区间的数是否够 \(k\),如果不够就在后面的区间里,但是此时前面是有贡献的,所以注意此时查询的 \(k\) 就要减去前面的数的个数了。

注意传入的节点是 \([l-1,r]\) 即可。
对了,此时两个节点要同时跳左右儿子哦。

代码如下:

ll query(int u, int v, int l, int r, int x)
{
	if(l == r) return l;//因为我们建的是权值线段树,此时 l 就是我们对应的值
	int k = nd[ls(v)].val - nd[ls(u)].val;
	int mid = l + r >> 1;
	if(k >= x) return query(ls(u), ls(v), l, mid, x);
	else return query(rs(u), rs(v), mid + 1, r, x - k);
}

全部代码

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
constexpr int N = 1e6 + 66;
int root[N], tot;
struct Seg_Ment_Tree{
	struct node{
		int val, ls, rs;
	}nd[N << 7];
#define ls(k) nd[k].ls
#define rs(k) nd[k].rs
	int newnode(int p){nd[++ tot] = nd[p];return tot;}
	void pushup(int p){nd[p].val = nd[ls(p)].val + nd[rs(p)].val;}
	void update(int &p, int l, int r, int x, int val)
	{
		p = newnode(p);
		if(l == r){nd[p].val += val;return ;}
		int mid = l + r >> 1;
		if(x <= mid) update(ls(p), l, mid, x, val);
		else update(rs(p), mid + 1, r, x, val);
		pushup(p);
	}
	ll query(int u, int v, int l, int r, int x)
	{
		if(l == r) return l;
		int k = nd[ls(v)].val - nd[ls(u)].val;
		int mid = l + r >> 1;
		if(k >= x) return query(ls(u), ls(v), l, mid, x);
		else return query(rs(u), rs(v), mid + 1, r, x - k);
	}
}seg;
int n, m, a;
int main()
{
	ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);
	cin >> n >> m;
	for(int i = 1;i <= n;i ++)
	{
		cin >> a;
		root[i] = root[i - 1];
		seg.update(root[i], 0, 1e9, a, 1);
	}
	for(int i = 1;i <= m;i ++)
	{
		int l, r, k;cin >> l >> r >> k;
		cout << seg.query(root[l - 1], root[r], 0, 1e9, k) << '\n';
	}
	return 0;
}

例题

1.P1533 可怜的狗狗

简要题意是和上面一样的,不重新打一遍了。
数据范围:\(1\le n \le 3\times 10^5 ,1\le m \le5\times10^4,0\le a_i<2^{31}\),且 \(a_i\) 互不相同。

其实这个就是模板二,注意空间限制比较小,离散化一下就可以了。

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
constexpr int N = 3e5 + 66;
int root[N], tot, inf = INT_MAX;
struct Seg_Ment_Tree{
	struct node{
		int val, ls, rs;
	}nd[N << 5];
#define ls(k) nd[k].ls
#define rs(k) nd[k].rs
	int newnode(int p){nd[++ tot] = nd[p];return tot;}
	void pushup(int p){nd[p].val = nd[ls(p)].val + nd[rs(p)].val;}
	void update(int &p, int l, int r, int x, int val)
	{
		p = newnode(p);
		if(l == r){nd[p].val += val;return ;}
		int mid = l + r >> 1;
		if(x <= mid) update(ls(p), l, mid, x, val);
		else update(rs(p), mid + 1, r, x, val);
		pushup(p);
	}
	int query(int u, int v, int l, int r, int x)
	{
		if(l == r) return l;
		int k = nd[ls(v)].val - nd[ls(u)].val;
		int mid = l + r >> 1;
		if(k >= x) return query(ls(u), ls(v), l, mid, x);
		else return query(rs(u), rs(v), mid + 1, r, x - k);
	}
}seg;
int n, m, a[N], b[N];
int main()
{
	ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);
	cin >> n >> m;
	for(int i = 1;i <= n;i ++)
	{
		cin >> a[i];b[i] = a[i];
	}
	sort(b + 1, b + 1 + n);
	for(int i = 1;i <= n;i ++) a[i] = lower_bound(b + 1, b + 1 + n, a[i]) - b;
	for(int i = 1;i <= n;i ++) 
	{
		root[i] = root[i - 1];
		seg.update(root[i], 1, n, a[i], 1);
	}
	for(int i = 1;i <= m;i ++)
	{
		int l, r, k;cin >> l >> r >> k;
		cout << b[seg.query(root[l - 1], root[r], 1, n, k)] << '\n';
	}
	return 0;
}

2.P3168 [CQOI2015] 任务查询系统

简要题意:
\(n\) 个任务,其统治区间为 \([l_i,r_i]\),权值为 \(p_i\)
询问 \(m\) 次,强制在线,询问对于 \(x_i\),权值最小的 \(k_i\) 的任务的权值和。

\(1\le m,n \le 10 ^ 5\)\(1\leq l_i\leq r_i\leq n\)\(1\le p_i \le 10^7\)\(x_i\)\(1\)\(n\) 的一个排列。

这不是逗我呢吗?《
主席树不是只能单点插入吗,怎么还要区间插入???

等等,你别急,主席树虽然只能单点插入,但是它的版本传递有类似于前缀和的作用,所以我们对于 \(l_i\)\(r_i + 1\) 进行权值线段树,但是差分就可以了。注意一个位置的数可以有许多,所以注意使用 vector 记录每个数。

查询时注意是权值和,所以我们要把左区间的贡献也算上。

代码:

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
constexpr int N = 1e5 + 66;
struct Persistent_Segment_Tree{
	struct node{
		ll val, su;
		int rs, ls;
	}nd[N << 7];
	int tot;
#define ls(k) nd[k].ls
#define rs(k) nd[k].rs
#define val(k) nd[k].val
#define su(k) nd[k].su
	void pushup(int p){val(p) = val(ls(p)) + val(rs(p));su(p) = su(ls(p)) + su(rs(p));}
	int newnode(int p){nd[++ tot] = nd[p];return tot;}
	void update(int &p, int l, int r, ll x, ll val)
	{
		p = newnode(p);
		if(l == r){val(p) += val;su(p) += x * val;return ;}
		int mid = l + r >> 1;
		if(x <= mid) update(ls(p), l, mid, x, val);
		else update(rs(p), mid + 1, r, x, val);
		pushup(p);
	}
	ll query(int p, int l, int r, ll x)
	{
		if(l == r) return min(su(p), x * l);//注意可能有多个 l,比较一下取最小即可
		int mid = l + r >> 1;
		if(x <= val(ls(p))) return query(ls(p), l, mid, x);
		else return query(rs(p), mid + 1, r, x - val(ls(p))) + su(ls(p));
	}
}seg;
ll Abs(ll x){return x > 0 ? x : -x;}
ll n, m, mx;
int root[N];
vector<ll> v[N];
int main()
{
	ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);
	cin >> m >> n;
	for(int i = 1;i <= m;i ++)
	{
		ll x, y, z;cin >> x >> y >> z;
		v[x].push_back(z), v[y + 1].push_back(-z);
		mx = max(mx, z);
	}
	for(int i = 1;i <= n;i ++)
	{
		root[i] = root[i - 1];
		for(auto p : v[i]) seg.update(root[i], 1, mx, Abs(p), (p > 0 ? 1ll : -1ll));
	}
	ll lst = 1;
	for(int i = 1;i <= n;i ++)
	{
		ll x, a, b, c;cin >> x >> a >> b >> c;
		a = (lst * a + b) % c + 1;
		lst = seg.query(root[x], 1, mx, a);
		cout << lst << '\n';
	}
	return 0;
}