观察本题题干,发现制约水最终的去处的为盘子的直径与容量。若对于每次询问,都直接暴力计算,显然会超时。于是想到一种能够减少判断次数,又不影响结果的方法—倍增。

首先预处理出从第 $i$ 个圆盘开始连续 $2^j$ 个圆盘中的最大直径,记为 $pre_{i,j}$。显然 $\forall i \in [1,n],pre_{i,0} = d_i$。然后就是倍增求最值的过程,这里就不赘述了。然后给出 $f_{i,j},g_{i,j}$,分别表示从 $i$ 开始水溢出 $2^j$ 次后所到达的圆盘与求水溢出所到达的 $2^j$ 个圆盘的容量之和。

但是现在还有一个问题,如果每次遍历去求第 $i$ 个圆盘再加入水后,最终能流到的圆盘,会额外产生 $O(N)$ 的复杂度,加上对所有圆盘的求解以及预处理后的查询,会达到 $O(N^2 \log N)$ 的复杂度,无法通过此题。不难发现,这其实存在单调性,因此使用二分答案去求解。设预处理的查询函数为 query (l,r),则有满足 query(i + 1,(l + r) >> 1) <= d[i] 的时候更新左端点为 l = ((l + r) >> 1) + 1,否则为 r = (l + r) >> 1。在得到答案 $l$ 后,就可以根据变量设置的含义去更新 $f_{i,0},g_{i,0}$,有 $f_{i,0} = l,g_{i,0} = c_{f_{i,0}}$。

由于 $N_{\max} = 10^5$,所以本题解将两个数组设置为 f[100005][20],g[100005][20]。则对于 $f_{i,j},g_{i,j}$ 的转移有:

1
2
3
for (int j = 1;j < 20;++j)
for (int i = 1;i <= n;++i)//i~i + 2^j = i ~ 2^(j - 1) + (i + 2^(j - 1) + 1)~2^j
f[i][j] = f[f[i][j - 1]][j - 1],g[i][j] = g[f[i][j - 1]][j - 1] + g[i][j - 1];

最后就是求解的部分了。考虑到题目存在流到最终的水池的情况,所以我们可以再加入一个容积为 $\inf$ 的圆盘。对于每一组的询问,首先判断水能否溢出,若可以,则利用倍增进行处理直至无法溢出,最后特判到达底盘的情况即可。

最后利用倍增的时间复杂度为 $O(n \log n)$。完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#define init(x) memset (x,0,sizeof (x))
#define ll long long
#define ull unsigned long long
#define INF 0x3f3f3f3f
using namespace std;
const int MAX = 1e5 + 5;
const int MOD = 1e9 + 7;
inline int read ();
int n,m,d[MAX],c[MAX];
int pre[MAX][20],f[MAX][20],g[MAX][20];
/*pre 预处理求区间最值
f 求从 i 开始水溢出 2^j 次后所到达的圆盘
g 求水溢出所到达的 2^j 个圆盘的容量之和 */
int query (int x,int y);
int main ()
{
//freopen (".in","r",stdin);
//freopen (".out","w",stdout);
n = read ();m = read ();
for (int i = 1;i <= n;++i) d[i] = read (),c[i] = read ();
for (int i = 1;i <= n;++i) pre[i][0] = d[i];//初始化
for (int j = 1;(1 << j) <= n;++j)//倍增求区间最值
for (int i = 1;i + (1 << j) - 1 <= n;++i)
pre[i][j] = max (pre[i][j - 1],pre[i + (1 << (j - 1))][j - 1]);
c[n + 1] = INF;f[n][0] = n + 1;g[n][0] = c[f[n][0]];//加入一个超大容积的底盘
for (int i = 1;i < n;++i)
{
int l = i + 1,r = n + 1;
while (l < r)//二分求会流入何处
{
int mid = (l + r) >> 1;
if (query (i + 1,mid) <= d[i]) l = mid + 1;//log 的查询
else r = mid;
}
f[i][0] = l;
g[i][0] = c[f[i][0]];//初始化
}
for (int j = 1;j < 20;++j)
for (int i = 1;i <= n;++i)
f[i][j] = f[f[i][j - 1]][j - 1],g[i][j] = g[f[i][j - 1]][j - 1] + g[i][j - 1];
for (int i = 1;i <= m;++i)
{
int x = read (),y = read ();
if (y > c[x])
{
y -= c[x];
for (int j = 19;j >= 0;--j)
if (y > g[x][j]) y -= g[x][j],x = f[x][j];
x = f[x][0];//注意别忘了减去一开始溢出的那一次
}
printf ("%d\n",x == (n + 1) ? 0 : x);//特判
}
return 0;
}
inline int read ()
{
int s = 0;int f = 1;
char ch = getchar ();
while ((ch < '0' || ch > '9') && ch != EOF)
{
if (ch == '-') f = -1;
ch = getchar ();
}
while (ch >= '0' && ch <= '9')
{
s = s * 10 + ch - '0';
ch = getchar ();
}
return s * f;
}
int query (int x,int y)
{
int k = (int) log2 (y - x + 1);
return max (pre[x][k],pre[y - (1 << k) + 1][k]);
}