[太阳神]小心危黑靈

题目

题目描述
大厂 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,si1,玩家总是会走到终点(最后一个房间)。“即若汝言,人族必亡;双足离地,前路不长!” —— 危黑靈

现在,有 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 n4×105 q ⩽ 5 × 1 0 4 q\leqslant 5\times 10^4 q5×104 。同时保证 1 ⩽ s i , p i , z i ⩽ 1 0 7 1\leqslant s_i,p_i,z_i\leqslant 10^7 1si,pi,zi107 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 z2si

注意这个 “原本无法战胜” 需要用某个 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 si2k 。在完成这个目标之前,会经历一个可能比较漫长的过程,这就是我们需要加速计算的地方。所以我们要考虑把状态转移图建出来。

显然分界点不超过 z z z,即 2 k ⩽ z 2^k\leqslant z 2kz,所以 s i < 2 k s_i<2^k si<2k 都是可战胜的,向 w i w_i wi 连边;而 s i ⩾ 2 k s_i\geqslant 2^k si2k 都向 l i l_i li 连边。那么现在只需要求出,从一个点出发,最多能走多远,仍然满足上面假定的胜负关系——第一个不能满足上面胜负关系的位置就是战胜了 s i ⩾ 2 k s_i\geqslant 2^k si2k 。出度唯一,可以直接倍增。我的天哪,壹道题复习两次倍增!毕竟走 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} si2k+1,且此时 z ⩾ s i z\geqslant s_i zsi 。那么,分界点 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];
}

猜你喜欢

转载自blog.csdn.net/qq_42101694/article/details/121395626