返回顶部

关于倍增ST表的一道小题

首先放上题面及题目链接
https://www.luogu.com.cn/problem/P7167
喷泉流水
问题描述
大家都知道喷泉吧?现在有一个喷泉由 N 个圆盘 组成,从上到下依次编号为 1∼N,第 i 个圆盘的直径为 Dᵢ,容量为 Cᵢ。
当一个圆盘里的水超过其容量时,水会溢出并向下流,仅流入直径大于当前圆盘的圆盘中。如果下方没有满足要求的圆盘,水会流到喷泉下的水池里。
询问规则
给定 Q 组询问,每组询问格式为:

向第 Rᵢ 个圆盘里倒入 Vᵢ 的水,求水最后会流到哪一个圆盘停止。
如果最终流入水池,输出 0。

注意:每个询问互不影响(即每次询问后,圆盘状态重置)。
输入格式

第一行两个整数 N, Q,代表圆盘数和询问数。
接下来 N 行,每行两个整数 Dᵢ, Cᵢ,代表第 i 个圆盘的直径和容量。
接下来 Q 行,每行两个整数 Rᵢ, Vᵢ,代表一组询问。

翻译一下题目就是对于每个询问查找其下方的第一个d大于他本身d的圆盘。判断下一个圆盘能否接住剩下的谁如果可以输出最后所在圆盘的编号。
否则继续向下寻找。

首先是暴力做法:对于每个询问,向下遍历一遍所有的圆盘。这个方法不过多说就是模拟。

然后更加进阶容易想到:用一个数组nxt来存储比当前圆盘的d更大的一个圆盘编号。然后对于每个询问直接依次跳nxt,直到水被消耗完。
对于这种方法也是我第一次的想法。
nxt数组很好找,开一个栈,每次入栈新元素后与栈顶元素比较大小,大于栈顶那么栈顶的nxt就是当前的元素。一直比较直到栈清空或者当前元素更小。

但是我在复杂度分析的时候忽略了每次询问的复杂度,超级容易被卡成n方的暴力算法。

该如何优化呢?

最后我想到了目前比较主流的解法——倍增加ST表。并去复习了一遍这个我平时比较少用的算法。收益良多。
此处浅浅解释一下倍增ST表吧。

倍增其实就是解决了链表这类数据结构在查询时的O(n)超大复杂度。因为每次查询都要从前往后一个个看。就如同我们的每次跳nxt操作。

联想到二分算法如何解决有序数组的数据查询速度优化的————每次砍一半将当前区间分成两个部分。但是链表并非有序的也无法像数组一样每次查询O(1)。但是链表有个优点就是链表也是有序的。这就给我们带来了将操作优化成log级别的希望。

想到二分——每次将目标区间对半分,排除了大量无用的数据。我们能不能将链表的每次查询也去除很多的无效信息呢?我们又发现———每次链表啊查询复杂度最大的浪费就是在每次链表向后跳的过程。如果一次跳两个,那我们的复杂度就会减半,一次三个/四个/五个...到底多少个呢。由此我们想到倍增。跳的步数为2的n次方。由于每个数字都有二进制的形式,那么无论距离是多少我们都可以将其拆分为不同点跳2的n次方步。那么我们就需要一个现成的表来记录每个点向后跳2的n次方步会到哪儿。由此诞生了ST表。ST[i][j]表示从i开始跳2的j次方步会到哪里。这样就可以把我们的查询复杂度降到log级别。
对于查询每次从大步向小步查。这样才能最大限度的降低复杂度,由此就可以得到最终Code:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline void read(int &n) {
	char c=getchar();
	bool f=0;
	n=0;
	while(!isdigit(c)) {
		f|=(c=='-');
		c=getchar();
	}
	while(isdigit(c)) {
		n=((n<<3)+(n<<1)+(c^48));
		c=getchar();
	}
	n=f?(-n):n;
	return ;
}
inline void llread(ll &n) {
	char c=getchar();
	bool f=0;
	n=0;
	while(!isdigit(c)) {
		f|=(c=='-');
		c=getchar();
	}
	while(isdigit(c)) {
		n=((n<<3)+(n<<1)+(c^48));
		c=getchar();
	}
	n=f?(-n):n;
	return ;
}

int n,q,r;
ll d[200005];
ll v;
//
int top=0;//
int sta[200005];//
//栈的构建
int nxt[200005][20];
//nxt[i][j]表示从i开始向后跳2的j次方个所需要的水量
int stp[200005][20];//
//stp[i][j]表示从i开始向后跳2的j次方个的id
int main() {
	read(n),read(q);
	for(int i=1; i<=n; ++i) {//
		nxt[i][0]=0;
		llread(d[i]);
		read(stp[i][0]);
	}
	top=1;
	sta[1]=1;
	for(int i=2; i<=n; ++i) {//
		while(top>0) {
			if(d[i]>d[sta[top]]) {
				nxt[sta[top--]][0]=i;
			} else {
				break;
			}
		}
		sta[++top]=i;
	}
	for(int j=1; (1<<j)<=n; ++j) {
		for(int i=n; i>=1; --i) {
			nxt[i][j]=nxt[nxt[i][j-1]][j-1];
			stp[i][j]=stp[i][j-1]+stp[nxt[i][j-1]][j-1];
		}
	}
	while(q--){
		read(r);//id
		llread(v);//水量
		for(int i=18;i>=0;--i){
			if(stp[r][i]&&stp[r][i]<v){
				v-=stp[r][i];
				r=nxt[r][i];
				
			}
		}
		printf("%d \n",r);
	}
	return 0;
}```
posted @ 2025-10-14 17:57  Noxaris  阅读(7)  评论(0)    收藏  举报
1 2 3 1
浏览器标题切换
浏览器标题切换end