「IOI2021」Dungeons
题目
点这里看题目。
分析
比较考察基础的观察和诡异的优化的题目,值得一试。
算法 1
直接模拟,复杂度为 \(O(qs)\)。
算法 2
移动的重复过程相当多,可以想到使用倍增压缩。
针对 lose
的情况写一个倍增可以得到 Subtask3 的分数。针对 win
的情况写一个倍增可以得到 Subtask2 的分数。
算法 3
单纯对于移动过程进行倍增,优化效果不明显。而注意到 \(z\) 只有当 \(< \max s\) 的时候才需要考虑限制,当 \(z\ge\max s\) 的时候图是确定的,直接倍增就好了。
容易想到的一个方向是,找到一种特殊的过程,使得经过这个过程后的 \(z'\) 保证可以达到 \(z\) 的某个倍数,从而完成对于执行复杂度的分析。我们并不能很快就关注到了一点:当 \(z\ge s_x\) 的时候,我们不会加上一个随机的数,而恰恰是 \(s_x\)。这其实就在暗示我们 \(z'\ge 2s_x\)。
我们发现了一个“两倍”的关系,虽然这和我们想要的不完全一致,但是已经可以带来一定的启发——我们可以对于 \(z\) 按照 2 的次幂划分阶段。我们面前是这样的两面情况:当不存在 \(z\ge s_x\) 这样的转移的时候,图的形态可以确定,也就意味着可以预处理;反过来,如果遇到了 \(z\ge s_x\) 这样的转移,我们只需要跨一步就可以进入到下一个阶段。
具体地,我们构造一个 \(G_t\),表示 \(z\in [2^t,2^{t+1})\) 时没有遇到 \(z\ge s_x\) 转移的图的形态。因此,对于 \(s_x<2^t\),我们连接边 \(x\overset{s_x}{\rightarrow} w_x\),而对于 \(s_x\ge 2^t\),我们连接 \(x\overset{p_x}{\rightarrow } l_x\)。这样,在一个确定的 \(G_t\),我们可以进行倍增,从而加速转移。跨阶段的情况则最多出现 \(\log s\) 次。
需要注意的是,当 \(t\ge \lfloor\log_2 \max s\rfloor\) 时,图上不存在 lose
边,而 \(z\) 也不再受权值的影响,此时的倍增上界为 \(\log_2 n\) 而不是 \(\log_2 s\)。
该算法的复杂度为 \(O((n+q)\log^2 s)\)。看起来不能通过,但是注意到 \(n\) 和 \(q\) 之间可以平衡复杂度,因此我们可以适当调低倍增上界,从而令预处理时间下降,询问时间上升,这样就能卡过去。
小结:
-
注意观察问题的特殊性,尤其是与常见条件不同的地方,这些往往是设置的突破口。某种程度上说,解决(人出的)问题也可以去思考出题人的意图。
-
注意这种“模拟”问题背后的值限制,我们往往可以去寻找一个令某个特征值“翻倍”的过程,从而分析出一个 \(O(\log x)\) 的复杂度。
-
学习一下平衡复杂度调节倍增的方法。
算法 4
NOIP 前写下这篇题解,目前看来这部分算法实战中价值不太大,先简单写一写。
注意到图 \(G_t\) 和 \(G_{t+1}\) 其实有很多重复的部分和少量的区别。我们可以基于此,找出每个图上的“关键”点——在两个图上出边有区别的点。如果我们不会模拟到这些关键点上,则在 \(G_t\) 和 \(G_{t+1}\) 上模拟是一致的;否则,我们又相当于确定了起点,此时单独在关键点之间倍增,问题就简单多了。
最终可以得到 \(O(n\log S+q\log^2 S)\) 的算法。
代码
算法 3
void Init() {
mx = 0;
rep( i, 1, N ) mx = MAX( mx, S[i] );
lg2 = log2( mx );
// lg2 = max{ x | 2^x <= mx }
// so 2^{lg2+1} > mx for sure
rep( t, 0, lg2 ) {
rep( i, 1, N ) {
int upper = ( 1 << ( t + 1 ) ) - 1;
if( S[i] < ( 1 << t ) ) {
go[t][i][0] = W[i];
gain[t][i][0] = S[i];
lim[t][i][0] = upper - S[i];
} else {
go[t][i][0] = L[i];
gain[t][i][0] = P[i];
lim[t][i][0] = MAX( 0, MIN( S[i] - 1, upper - P[i] ) );
}
}
lim[t][N + 1][0] = 0;
gain[t][N + 1][0] = 0;
go[t][N + 1][0] = N + 1;
rep( j, 1, lg2 ) rep( i, 1, N + 1 ) {
go[t][i][j] = go[t][go[t][i][j - 1]][j - 1];
gain[t][i][j] = gain[t][i][j - 1] + gain[t][go[t][i][j - 1]][j - 1];
lim[t][i][j] = MAX( 0ll, MIN( ( LL ) lim[t][i][j - 1], lim[t][go[t][i][j - 1]][j - 1] - gain[t][i][j - 1] ) );
}
}
int t = lg2 + 1;
rep( i, 1, N )
go[t][i][0] = W[i],
gain[t][i][0] = S[i];
gain[t][N + 1][0] = 0;
go[t][N + 1][0] = N + 1, fin = log2( N );
rep( j, 1, fin ) rep( i, 1, N + 1 ) {
go[t][i][j] = go[t][go[t][i][j - 1]][j - 1];
gain[t][i][j] = gain[t][i][j - 1] + gain[t][go[t][i][j - 1]][j - 1];
}
}
LL Simulate( int x, LL z ) {
for( int t = ( int ) log2( z ) ; t <= lg2 ; t ++ ) {
for( int i = lg2 ; ~ i ; i -- )
if( z <= lim[t][x][i] )
z += gain[t][x][i],
x = go[t][x][i];
if( x == N + 1 ) break;
if( S[x] < ( 1 << t ) || z >= S[x] )
z += S[x], x = W[x];
else if( z < S[x] )
z += P[x], x = L[x];
}
if( x != N + 1 ) {
int t = lg2 + 1;
for( int i = fin ; ~ i ; i -- )
if( go[t][x][i] != N + 1 )
z += gain[t][x][i], x = go[t][x][i];
z += S[x];
}
return z;
}
算法 4
#include
#include
typedef long long LL;
const int INF = 0x3f3f3f3f;
const int MAXN = 4e5 + 5, MAXLOG = 25;
template
void read( _T &x ) {
x = 0; char s = getchar(); bool f = false;
while( s < '0' || '9' < s ) { f = s == '-', s = getchar(); }
while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
if( f ) x = -x;
}
template
void write( _T x ) {
if( x < 0 ) putchar( '-' ), x = -x;
if( 9 < x ) write( x / 10 );
putchar( x % 10 + '0' );
}
template
_T MIN( const _T a, const _T b ) {
return a < b ? a : b;
}
template
_T MAX( const _T a, const _T b ) {
return a > b ? a : b;
}
LL gain[MAXN][MAXLOG];
int go[MAXN][MAXLOG];
LL jmp[MAXN][MAXLOG];
int nxt[MAXN][MAXLOG], lim[MAXN][MAXLOG];
LL wei[MAXN][MAXLOG];
int bound[MAXN][MAXLOG], tar[MAXN][MAXLOG];
bool vis[MAXN];
int S[MAXN], P[MAXN], W[MAXN], L[MAXN];
int up, dn;
int N, Q, lg2, fin;
void DFS( const int u, const int t ) {
if( ~ tar[u][t] || vis[u] ) return ;
vis[u] = true;
if( S[u] < ( 1 << t ) ) {
DFS( W[u], t );
tar[u][t] = tar[W[u]][t];
wei[u][t] = wei[W[u]][t] + S[u];
bound[u][t] = MAX( 0, MIN( up - 1, bound[W[u]][t] ) - S[u] );
} else {
DFS( L[u], t );
tar[u][t] = tar[L[u]][t];
wei[u][t] = wei[L[u]][t] + P[u];
bound[u][t] = MAX( 0, MIN( S[u] - 1, MIN( up - 1, bound[L[u]][t] ) - P[u] ) );
}
}
void Init() {
int mx = 0;
for( int i = 0 ; i < N ; i ++ )
mx = MAX( mx, S[i] );
lg2 = log2( mx ), fin = log2( N );
for( int t = 0 ; t <= lg2 ; t ++ ) {
up = ( 1 << ( t + 1 ) ), dn = 1 << t;
for( int i = 0 ; i <= N ; i ++ )
tar[i][t] = -1, bound[i][t] = 0, vis[i] = false;
for( int i = 0 ; i <= N ; i ++ )
if( i == N || ( dn <= S[i] && S[i] < up ) )
tar[i][t] = i, wei[i][t] = 0, bound[i][t] = INF;
for( int i = 0 ; i <= N ; i ++ ) DFS( i, t );
for( int i = 0 ; i < N ; i ++ )
if( dn <= S[i] && S[i] < up ) {
if( ~ tar[L[i]][t] ) {
nxt[i][0] = tar[L[i]][t];
jmp[i][0] = P[i] + wei[L[i]][t];
lim[i][0] = MAX( 0, MIN( MIN( up - 1 - P[i], S[i] - 1 ),
bound[L[i]][t] - P[i] ) );
} else
nxt[i][0] = -1, jmp[i][0] = 0, lim[i][0] = 0;
}
}
nxt[N][0] = N, lim[N][0] = 0;
for( int j = 1 ; j <= lg2 ; j ++ )
for( int i = 0 ; i <= N ; i ++ ) {
int p = nxt[i][j - 1];
if( p == -1 || nxt[p][j - 1] == -1 ) {
nxt[i][j] = -1;
jmp[i][j] = lim[i][j] = 0;
continue;
}
nxt[i][j] = nxt[p][j - 1];
jmp[i][j] = jmp[i][j - 1] + jmp[p][j - 1];
lim[i][j] = MAX( 0ll, MIN( ( LL ) lim[i][j - 1], lim[p][j - 1] - jmp[i][j - 1] ) );
}
gain[N][0] = 0, go[N][0] = N;
for( int i = 0 ; i < N ; i ++ )
gain[i][0] = S[i], go[i][0] = W[i];
for( int j = 1 ; j <= fin ; j ++ )
for( int i = 0 ; i <= N ; i ++ )
go[i][j] = go[go[i][j - 1]][j - 1],
gain[i][j] = gain[i][j - 1] + gain[go[i][j - 1]][j - 1];
}
LL Query( int x, LL z ) {
for( int t = 0 ; t <= lg2 ; t ++ ) {
if( z >= ( 1 << ( t + 1 ) ) ) continue;
if( z > bound[x][t] || tar[x][t] == -1 ) continue;
z += wei[x][t], x = tar[x][t];
for( int i = lg2 ; ~ i ; i -- )
if( ~ nxt[x][i] && z <= lim[x][i] )
z += jmp[x][i], x = nxt[x][i];
if( x == N ) break;
if( z < S[x] ) z += P[x], x = L[x];
else z += S[x], x = W[x];
}
if( x != N ) {
for( int i = fin ; ~ i ; i -- )
if( go[x][i] != N )
z += gain[x][i], x = go[x][i];
z += S[x];
}
return z;
}
int main() {
freopen( "reflection.in", "r", stdin );
freopen( "reflection.out", "w", stdout );
read( N ), read( Q );
for( int i = 0 ; i < N ; i ++ ) read( S[i] );
for( int i = 0 ; i < N ; i ++ ) read( P[i] );
for( int i = 0 ; i < N ; i ++ ) read( W[i] );
for( int i = 0 ; i < N ; i ++ ) read( L[i] );
Init();
while( Q -- ) {
int a, b;
read( a ), read( b );
write( Query( a, b ) ), putchar( '\n' );
}
return 0;
}