【题解】[AGC012E]Camel and Oases

给定 $n$ 个绿洲,第 $i$ 个绿洲的坐标为 $x_i$,保证 $-10^{9}\le x_1<x_2…<x_n\le 10^9$

现在有一个人在沙漠中进行旅行,他初始的背包的水容积为 $V$ 升,同时他初始拥有 $V$ 升水,每次到达一个绿洲时,他拥有的水的量将自动重置为容积上限。他现在可以选择两个操作来进行旅行:

  1. 走路,行走距离为 $d$ 时,需要消耗 $d$ 升水。清注意,任意时刻你拥有的水的数量不能为负数。

  2. 跳跃,令 $v$ 为你当前拥有的水量,若 $v>0$,则你可以跳跃至任意一个绿洲,然后重置容积上界和所拥有的水量为 $v/2$ (四舍五入取整)。

对于每一个 $i$ 满足 $1\le i\le n$,你需要求当你在第 $i$ 个绿洲作为起点时,你能否依次遍历其他所有绿洲。如果可以,输出 Possible,否则输出 Impossible

$1\leq n,v\leq 2\times10^5$。

神仙状态设计题orz

定义 $G$ 为全集,具体定义后文详谈。

首先考虑一点,就是本质上最多有 $\log V$ 种容积,可以把这个看做 $\log V$ 层,每层都有 $n$ 个点。定义层与层之间的方向「从上至下」代表容积不断变小,同上从上至下给每一层一个递增的编号。考虑对于每一层,可以把所有能够互相到达的连通块缩成几条线段,这样每层就是几条线段了。那么题目要求的就是,在限制第一层选某个线段时,每层至多选一个线段,是否可以讲整个 $[1,n]$ 覆盖。

考虑首先预处理出来 $L[s][1\sim n]$ 和 $R[s][1\sim n]$ ,分别表示在第 $s$ 层内,某个点所在线段的左端点和右端点。然后考虑预处理出这么两个状态:$expanL_t$ 和 $expanR_t$ ,表示只考虑集合 $t$ 内的那几层,最多从 $1$ 向右扩展至什么地方和最多从 $n$ 向左扩展到什么地方。考虑先把所有包含第一层的集合忽略,那么存在转移:

这个转移十分神奇。以 $expanL$ 的转移为例,本质上对于一个集合 $t-\{s\}$,为了求出连续块,应该去枚举第 $s$ 层内第一个不包含在 $expanL_{t-\{s\}}$ 内的线段,然后用这个线段是右端点来更新,所以或许需要一个二分。但是其实根本不需要,直接可以用上面推出来的 $L[s][1\sim n]$ 和 $R[s][1\sim n]$ 进行转移,还是很巧妙的。

最后就可以枚举每条线段,去找是否存在一个不包含第一层的状态 $t$ , $expanL_t$ 覆盖了 $1\sim l_t$ ,同时 $expanR_{G-t}$ 覆盖了 $r_{G-t}\sim n$ ,那么如果这个线段的左右端点 $l,r$ 分别满足 $l-1\leq l_t$ 和 $r+1\geq r_{G-t}$,那么这个线段内部的点都是合法的。

最终复杂度 $O(\max\{(n+v)\log v, 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
#include <cstdio> 
#include <iostream>
#include <algorithm>

using namespace std ;

#define l first
#define r second

const int M = 40 ;
const int N = 1200010 ;
const int MAX = 1001000001 ;

int dep ;
int maxn ;
int ans[N] ;
int n, m, k ;
int base[N] ;
int L[M][N] ;
int R[M][N] ;
int farfrl[N] ;
int farfrr[N] ;
pair<int, int> rg[N] ;

void Init_dp(){
for (int j = m ; j >= 0 ; j >>= 1){
R[++ dep][n + 1] = n + 1 ;
for (int i = 1 ; i <= n ; ++ i)
L[dep][i] = (base[i] - base[i - 1] > j) ? i : L[dep][i - 1] ;
for (int i = n ; i >= 1 ; -- i)
R[dep][i] = (base[i + 1] - base[i] > j) ? i : R[dep][i + 1] ;
if (!j) break ;
}
return ;
}
void Auxiliary_dp(){
for (int i = 0 ; i <= maxn ; i += 2)
for (int j = 1 ; j <= dep ; ++ j)
if (1 << (j - 1) & i){
farfrl[i] = max(farfrl[i], R[j][ farfrl[i ^ (1 << (j - 1))] + 1]) ;
farfrr[i] = min(farfrr[i], L[j][ farfrr[i ^ (1 << (j - 1))] - 1]) ;
}
return ;
}
int main(){
cin >> n >> m ;
base[0] = -MAX ;
base[n + 1] = MAX ; k = -1 ;
for (int i = 1 ; i <= n ; ++ i)
scanf("%d", &base[i]) ; Init_dp() ;
for (int i = 1 ; i <= n ; ++ i)
rg[i].l = L[1][i], rg[i].r = R[1][i] ;
sort(rg + 1, rg + n + 1) ; maxn = (1 << dep) - 1 ;
for (int i = 0 ; i <= maxn ; ++ i) farfrr[i] = n + 1 ;
k += unique(rg + 1, rg + n + 1) - rg ; Auxiliary_dp() ;
for (int i = 1 ; i <= k ; ++ i)
for (int j = 0 ; j <= maxn ; j += 2)
if (farfrl[j] + 1 >= rg[i].l && farfrr[(maxn ^ j) ^ 1] - 1 <= rg[i].r)
{ for (int o = rg[i].l ; o <= rg[i].r ; ++ o) ans[o] = 1 ; break ; }
for (int i = 1 ; i <= n ; ++ i) puts(ans[i] ? "Possible" : "Impossible") ; return 0 ;
}
/*
0 8
0 8
1 7
0 8
1 7
0 8
2 6
0 8
*/