題目
點這里看題目。
分析
比較考察基礎的觀察和詭異的優化的題目,值得一試。
算法 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
注意到圖 \(G_t\) 和 \(G_{t+1}\) 其實有很多重復的部分和少量的區別。我們可以基於此,找出每個圖上的“關鍵”點——在兩個圖上出邊有區別的點。如果我們不會模擬到這些關鍵點上,則在 \(G_t\) 和 \(G_{t+1}\) 上模擬是一致的,為了加速模擬我們可以直接升級到 \(G_{t+1}\)。
區別就出現在 \(2^t \le s_x< 2^{t+1}\) 的結點 \(x\) 上,因此在圖 \(G_t\) 中這樣的 \(x\),加上結點 \(n\) 就是我們的關鍵點。
考慮我們的行走過程,我們從 \(G_t\) 的 \(u\) 出發,而前路只有兩種可能:
-
我們走着走着就發覺自己的 level 超過 \(G_t\),並且當前還沒有經過關鍵點,於是我們可以假裝一開始就在 \(G_{t+1}\) 上面行走,從而跳過這個階段;
-
我們確實走到了一個關鍵點,那么我們不得不去解決在 \(G_t\) 上行走的問題。不過,如果我們在關鍵點上不會進入 \(G_{t+1}\),而是再向前一步,就又回到了一開始的問題,也就是消除了這個關鍵點的影響。
相似的問題中,我們於是會去關心下一個關鍵點,重復……
所以,關鍵點之間的移動是一個鏈式過程,我們同樣可以處理 \(G_t\) 上關鍵點之間的倍增從而加速關鍵點之間的模擬過程。
涉及到的其實就是每個點會不會走到關鍵點、會走到哪些的問題,在每層圖上可以記憶化搜索。由於每個結點只會當一次關鍵點,因此預處理的時間被壓縮到了 \(O(n\log n+n\log S)\)。
最終可以得到 \(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 <cmath>
#include <cstdio>
typedef long long LL;
const int INF = 0x3f3f3f3f;
const int MAXN = 4e5 + 5, MAXLOG = 25;
template<typename _T>
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<typename _T>
void write( _T x ) {
if( x < 0 ) putchar( '-' ), x = -x;
if( 9 < x ) write( x / 10 );
putchar( x % 10 + '0' );
}
template<typename _T>
_T MIN( const _T a, const _T b ) {
return a < b ? a : b;
}
template<typename _T>
_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() {
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;
}