题目
传送门 to UOJ(原题要求实现函数;这里就改为传统题吧)
题目描述
大厂
D
o
g
i
z
i
p
\rm Dogizip
Dogizip 的新游戏《小心!危黑靈》正在风靡全球!
这款游戏是这样的:共有 ( n + 1 ) (n+1) (n+1) 个房间,其中最后一个房间是终点,其余的房间都有一条 D D G \sf DDG DDG,每条 D D G \sf DDG DDG 有自己的能力值 s i s_i si 。玩家最初有一个能力值 z z z,当玩家到达非终点的 i i i 号房间时,进行判定:
- 如果当前能力值不小于 s i s_i si,则玩家战胜了这个房间内的 D D G \sf DDG DDG,玩家的能力值将会增加 s i s_i si,并被传送到 w i w_i wi 号房间。注意,一个房间内的 D D G \sf DDG DDG 可以被多次战胜,玩家的能力值也会相应地多次增加。“吾乃旧神,身躯永存!” —— 危黑靈
- 如果当前能力值小于 s i s_i si,则玩家被房间内的 D D G \sf DDG DDG 给「随切」掉了!玩家的能力值仍然会增加 p i p_i pi,并被传送到 l i l_i li 号房间。
由于 w i > i w_i>i wi>i 且 p i , s i ⩾ 1 p_i,s_i\geqslant 1 pi,si⩾1,玩家总是会走到终点(最后一个房间)。“即若汝言,人族必亡;双足离地,前路不长!” —— 危黑靈
现在,有 q q q 个人使用了相同的地图设置(即相同的 w , l , s , p w,l,s,p w,l,s,p 序列),第 i i i 个人从 x i x_i xi 房间开始游戏,并且最初能力值是 z i z_i zi,请问他走到终点时的能力值是多少?
数据范围与提示
n
⩽
4
×
1
0
5
n\leqslant 4\times 10^5
n⩽4×105 但
q
⩽
5
×
1
0
4
q\leqslant 5\times 10^4
q⩽5×104 。同时保证
1
⩽
s
i
,
p
i
,
z
i
⩽
1
0
7
1\leqslant s_i,p_i,z_i\leqslant 10^7
1⩽si,pi,zi⩽107 且
w
i
>
i
w_i>i
wi>i 。
思路
蹊跷之处在于,战胜 s i s_i si 后将会立刻增加 s i s_i si 的能力值。这明显就很像倍增——原本无法战胜,即 z < s i z<s_i z<si,最终把它战胜了,那么必然得到 z ′ ⩾ 2 s i z'\geqslant 2s_i z′⩾2si 。
注意这个 “原本无法战胜” 需要用某个
z
z
z 作为分界点吗?其实分界点一直是 静态 的。即,分界点是你给定的某个值,而不是你当前的
z
z
z 。显然你战胜 “目前无法战胜” 的
D
D
G
\sf DDG
DDG 的概率为
0
0
0 。就像用格洛克玩轮盘赌。
如果是静态的,那就有可能预处理。再联想到我们的倍增,当然是选择 2 k 2^k 2k 作为分界点。那么目标就是战胜一个 s i ⩾ 2 k s_i\geqslant 2^k si⩾2k 。在完成这个目标之前,会经历一个可能比较漫长的过程,这就是我们需要加速计算的地方。所以我们要考虑把状态转移图建出来。
显然分界点不超过
z
z
z,即
2
k
⩽
z
2^k\leqslant z
2k⩽z,所以
s
i
<
2
k
s_i<2^k
si<2k 都是可战胜的,向
w
i
w_i
wi 连边;而
s
i
⩾
2
k
s_i\geqslant 2^k
si⩾2k 都向
l
i
l_i
li 连边。那么现在只需要求出,从一个点出发,最多能走多远,仍然满足上面假定的胜负关系——第一个不能满足上面胜负关系的位置就是战胜了
s
i
⩾
2
k
s_i\geqslant 2^k
si⩾2k 。出度唯一,可以直接倍增。我的天哪,壹道题复习两次倍增!毕竟走
s
s
s 步之后,你将战无不胜,而一直胜利则必然走到终点。
如果直接这样求解,复杂度是 O [ ( n + q ) log 2 s ] \mathcal O[(n+q)\log^2 s] O[(n+q)log2s] 的。可是 n n n 似乎明显比 q q q 大一点。难道说,在预处理上我们可以做到更优吗?
这个优化还是需要一些想象力的。分界点为 2 k 2^k 2k 和 2 k + 1 2^{k+1} 2k+1 的唯一区别就是 s i ∈ [ 2 k , 2 k + 1 ) s_i\in[2^k,2^{k+1}) si∈[2k,2k+1) 的假定胜负情况。巧妙的是,如果在走到这样的点之前,就已经不能满足我们的假定胜负关系了,那一定是走到了 s i ⩾ 2 k + 1 s_i\geqslant 2^{k+1} si⩾2k+1,且此时 z ⩾ s i z\geqslant s_i z⩾si 。那么,分界点 2 k + 1 2^{k+1} 2k+1 的假定胜负情况就是正确的!
所以 如果不能走到这样的点,则二者无异。这个巧妙的结论,会很轻易地导向一个复杂度的优化。
为了让分界点 2 k 2^k 2k 有用,无论我走到哪里,都会 “嗖” 的一下被吸到 s i ∈ [ 2 k , 2 k + 1 ) s_i\in[2^k,2^{k+1}) si∈[2k,2k+1) 的点——假如 “吸不进去”,那么这张图就没用了。于是,我的出发点总是 s i ∈ [ 2 k , 2 k + 1 ) s_i\in[2^k,2^{k+1}) si∈[2k,2k+1),倍增数组也只需要在这些点上进行处理。于是倍增数组的预处理变为了 O ( n log s ) \mathcal O(n\log s) O(nlogs) 。
最后补充一句:万一所有 k k k 都不能让一个点被 “吸走” 呢?所以规定终点也是特殊点之一。当 k k k 充分大时,一直赢就会赢到终点,就可以被 “吸走” 了。
当然,对于每张图我们仍然要预处理会被 “吸” 到哪个点,也就是最近的一个有用点。出度唯一,用记忆化 d f s \rm dfs dfs 可以轻易求解。这个的复杂度也是 O ( n log s ) \mathcal O(n\log s) O(nlogs),预处理就结束了!
查询仍是简单倍增。时间复杂度 O ( n log s + q log 2 s ) \mathcal O(n\log s+q\log^2 s) O(nlogs+qlog2s) 。
代码
不幸的是,递归会爆栈。所以我只能模拟递归。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <climits>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long llong;
inline int readint(){
int a = 0, c = getchar(), f = 1;
for(; !isdigit(c); c=getchar())
if(c == '-') f = -f;
for(; isdigit(c); c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
const int MAXN = 400005;
int w[MAXN], l[MAXN], s[MAXN], p[MAXN];
const llong INFTY = LONG_LONG_MAX>>1;
struct Node{
llong val, jie; int dest;
Node() = default;
Node(const llong &_val,const llong &_jie,int _dest = -1)
:val(_val), jie(_jie), dest(_dest){ }
static Node DEAD(){ return Node(0,-1); }
static Node I(int x){ return Node(0,INFTY,x); }
Node operator + (const Node &b) const {
return Node(val+b.val,min(jie,b.jie-val),b.dest);
}
};
const int LOGS = 25;
Node fa[MAXN][LOGS], closer[LOGS][MAXN];
bool vis[MAXN];
int sta[MAXN], top; ///< stack stimulate recurse
int main(){
int n = readint(), q = readint();
rep(i,1,n) s[i] = readint();
rep(i,1,n) p[i] = readint();
rep(i,1,n) w[i] = readint()+1;
rep(i,1,n) l[i] = readint()+1;
int lens = *max_element(s+1,s+n+1);
if(lens < n) lens = n; // avoid mistake
lens = 32-int(__builtin_clz(lens)); // highbit
for(int level=0; level<=lens; ++level){
memset(vis+1,false,n+1);
rep(i,1,n+1) if(!vis[i]){
for(sta[top=1]=i; true; ){
const int &x = sta[top];
if(vis[x]) break; // exit recursing
vis[x] = true; // remember to put tag!
if(x == n+1 || (s[x]>>level) == 1){
closer[level][x] = Node::I(x);
break; // find key point
}
closer[level][x] = Node::DEAD(); // avoid circle
int to = (s[x] < (1<<level)) ? w[x] : l[x];
sta[++ top] = to; // virtual recurse
}
while((-- top) != 0){
int to = sta[top+1]; Node now(0,INFTY);
const int &x = sta[top];
if(s[x] < (1<<level)) now.val = s[x];
else now.val = p[x], now.jie = s[x]-1;
closer[level][x] = now+closer[level][to];
}
}
rep(i,1,n) if((s[i]>>level) == 1)
fa[i][0] = Node(p[i],s[i]-1)+closer[level][l[i]];
}
rep(j,0,lens-1) rep(i,1,n) if(~fa[i][j].dest)
fa[i][j+1] = fa[i][j]+fa[fa[i][j].dest][j];
for(int pos; q; --q){
pos = readint()+1; llong now = readint();
for(int level=0; pos!=n+1; ++level){
while((now>>level) > 1) ++ level;
if(level > lens) level = lens; // last flight
if(now > closer[level][pos].jie)
continue; // skip the level
now += closer[level][pos].val;
pos = closer[level][pos].dest;
for(int j=lens; ~j&&pos!=n+1; --j)
if(now <= fa[pos][j].jie){
now += fa[pos][j].val;
pos = fa[pos][j].dest;
}
if(pos == n+1) break;
if(now < s[pos]) now += p[pos], pos = l[pos];
else now += s[pos], pos = w[pos]; // simulate
}
printf("%lld\n",now);
}
return 0;
}
d f s \rm dfs dfs 的原型是:
void dfs(int x){
vis[x] = true; // visited
if(x == n+1 || (s[x]>>level) == 1)
return void(closer[level][x] = Node::I(x));
closer[level][x] = Node::DEAD(); // avoid circle
int to; Node now(0,INFTY);
if(s[x] < (1<<level)) to = w[x], now.val = s[x];
else to = l[x], now.val = p[x], now.jie = s[x]-1;
if(!vis[to]) dfs(to); // recurse
closer[level][x] = now+closer[level][to];
}