题目描述
难度:[蓝]提高+/省选-
大家都知道喷泉吧?现在有一个喷泉由N个圆盘组成,从上到下以此编号为1∼N,第i个喷泉的直径为Di,容量为Ci,当一个圆盘里的水大于了这个圆盘的容量,那么水就会溢出往下流,直到流入半径大于这个圆盘的圆盘里。如果下面没有满足要求的圆盘,水就会流到喷泉下的水池里。
现在给定Q组询问,每一组询问这么描述:
- 向第Ri个圆盘里倒入Vi的水,求水最后会流到哪一个圆盘停止。如果最终流入了水池里,那么输出0。
注意,每个询问互不影响。
输入格式
第一行两个整数N,Q代表圆盘数和询问数。
接下来N行每行两个整数Di,Ci代表一个圆盘。
接下来Q行每行两个整数Ri,Vi代表一个询问。
输出格式
Q行每行一个整数代表询问的答案。
输入样例
6 5
4 10
6 8
3 5
4 14
10 9
4 20
1 25
6 30
5 8
3 13
2 8
输出样例
5
0
5
4
2
算法
倍增+二分
这是今天进阶版的茶,如果每个询问不是相互独立的,就和今天茶的解法基本一样,只要用单调栈预处理出每个盘子i的右边第一个直径大于自己的位置down[i]就可以了。唯一的区别是并查集合并i和find(down[i])两个节点,而不是i和find(i+1)。但遗憾的是,本题的询问都是相互独立的,如果用并查集的话还需要进行撤回操作,时间复杂度太高了。
我们可以采用倍增来预处理出两个信息p和sum,p[i][j]表示盘子i向后走2j步所能到达的盘子编号,sum[i][j]表示盘子i向后走2j步所经过盘子的总体积(不包括盘子i)。
然后对于每个询问,我们二分找出Ri往后走多少步会使得路径上所有盘子的体积刚好大于Vi,那个盘子就是Vi体积的水倒进盘子Ri后最终流向的盘子。
复杂度分析
时间复杂度
倍增预处理出p的时间复杂度为O(nlog2n)。之后对于每个询问i,需要先二分答案找到Ri走多少步可以使得路径上盘子的容积大于Vi,时间复杂度为O((log2n)2),一个log是二分,还有一个log是check(需要用二进制来凑给定的步数),所以处理所有询问的时间复杂度为O(m(log2n)2)。
综上,算法整体的时间复杂度为O(nlog2n+m(log2n)2)。
空间复杂度
除去题目输入的数组,空间消耗主要是p和sum两个数组,额外空间复杂度为O(nlog2n)。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int n, q, d[N], c[N], p[N][18], sum[N][18];
int main() {
scanf("%d%d", &n, &q);
for(int i = 1; i <= n; i++) {
scanf("%d%d", &d[i], &c[i]);
p[i][0] = n + 1;
sum[i][0] = 0;
}
stack<int> stk;
for(int i = 1; i <= n; i++) {
while(!stk.empty() && d[stk.top()] < d[i]) {
p[stk.top()][0] = i;
sum[stk.top()][0] = c[i];
stk.pop();
}
stk.push(i);
}
for(int j = 0; j < 17; j++) {
for(int i = 1; i <= n; i++) {
p[i][j + 1] = p[p[i][j]][j];
sum[i][j + 1] = sum[i][j] + sum[p[i][j]][j];
}
}
function<int(int, int)> check = [&](int src, int k) {
int x = src, s = c[src];
for(int j = 0; j <= 17; j++) {
if(k>>j&1) {
s += sum[x][j];
x = p[x][j];
}
}
return s;
};
for(int index = 1; index <= q; index++) {
int ri, vi;
scanf("%d%d", &ri, &vi);
if(c[ri] >= vi) {
printf("%d\n", ri);
}else {
int l = 1, r = n;
while(l < r) {
int mid = l + r >> 1;
if(check(ri, mid) >= vi) {
r = mid;
}else {
l = mid + 1;
}
}
int k = r, x = ri;
for(int j = 0; j <= 17; j++) {
if(k>>j&1) {
x = p[x][j];
}
}
if(x == n + 1) {
puts("0");
}else {
printf("%d\n", x);
}
}
}
return 0;
}