POI 18 Stage 3 Meteors ( Parallel Binary Search, Fenwick Tree )
http://main.edu.pl/en/archive/oi/18/met
題意:
有 M 個環狀的地,每個地屬於一個人。有 K 次隕石事件,每次會在某一連續區間 [ L[ i ], R[ i ] ] 每個地分別降下有 W[ i ] 單位的隕石。有 N 個人,每個人有最少需要收集的隕石量 P[ i ]。要求分別輸出每個人在第幾次隕石事件會達到目標,或是不可達成。
資料規模:
N, M, K ≤ 3e5
MAXP, MAXA ≤ 1e9
TL: 10000 ms
ML: 64 MB
解法:
很直觀的做法是開一個持久化線段樹,維護每一次操作後的隕石狀態,這麼一來可以對所有人分別對答案做二分搜,檢查只需要 O( lg M ) 時間,因此時間複雜度會是 O( ( N + M ) * lg M * lg K ),但空間複雜度會是 O( ( M + K ) lg M ),超過限制。
考慮離線算法,做整體二分。每層重新在資料結構上更新該層左半的貢獻,就能在和一個人的土地數量乘上 lg M 的複雜度內判斷一個人的答案在左半,或是右半,達到二分的效果。注意任一層都只有 N 人,擁有的總土地數量是 M。由於已到右半的所有人,左半的貢獻是確定發生的,所以在往右分之前就可以先分別減去,如此一來就能在遞迴前將左半的貢獻清空,每次就可以以乾淨的資料結構開始。
實作上要注意的是,這個資料結構雖然用線段樹是最直觀合理的,但會因為過慢只能拿到 50 分。
這裡有一個技巧,就是 Fenwick Tree 可以做到 O( lg M ) 的區間修改和單點詢問,具體做法是,和平常直接儲存元素不同,而是儲存該元素和前一元素的差。在所有差分之前補 0,顯然長度為 k 的前綴和就是第 k 個元素的值。
時間 / 空間複雜度:
O( ( N + M ) * lg K * lg M ) / O( N + M )
#include <bits/stdc++.h> using namespace std; const int MAXN = ( int ) 3e5; const int MAXM = ( int ) 3e5; const int MAXK = ( int ) 3e5; int N, M; int O[ MAXM ]; // owner of field vector< int > rO[ MAXN ]; // owner to field int P[ MAXN ]; // goal int K; int L[ MAXK ], R[ MAXK ], W[ MAXK ]; // [ L, R ], might be cirucular int ans[ MAXN ]; struct Fenwick_Tree{ long long sum[ MAXM + 10 ]; // sum[ 1 ] = A[ 0 ], sum[ 1 ] + sum[ 2 ] = A[ 1 ] void add( int ql, int qr, int v ){ // [ ql, qr ) - 0 base ++ql, ++qr; for( int i = ql; i < M + 5; i += i & -i ){ sum[ i ] += v; } for( int i = qr; i < M + 5; i += i & -i ){ sum[ i ] -= v; } } long long gs( int x ){ // A[ x ] = S[ 1, x + 1 ] ++x; long long res = 0LL; for( int i = x; i; i -= i & -i ){ res += sum[ i ]; } return res; } } ftree; void parallel_bs( int lb, int rb, vector< int > &nation ){ if( lb + 1 == rb ){ for( int i = 0; i < nation.size(); ++i ){ int owner = nation[ i ]; for( int j = 0; j < rO[ owner ].size(); ++j ){ if( L[ lb ] <= R[ lb ] ){ if( L[ lb ] <= rO[ owner ][ j ] and rO[ owner ][ j ] <= R[ lb ] ){ P[ owner ] -= W[ lb ]; } } else{ if( rO[ owner ][ j ] <= R[ lb ] or L[ lb ] <= rO[ owner ][ j ] ){ P[ owner ] -= W[ lb ]; } } if( P[ owner ] <= 0 ) break; } if( P[ owner ] <= 0 ){ ans[ owner ] = rb; } else{ ans[ owner ] = -1; } } return; } int mid = lb + rb >> 1; { // update contribution of [ lb, mid ) to ftree for( int i = lb; i < mid; ++i ){ if( L[ i ] <= R[ i ] ){ ftree.add( L[ i ], R[ i ] + 1, W[ i ] ); } else{ ftree.add( 0, R[ i ] + 1, W[ i ] ); ftree.add( L[ i ], M, W[ i ] ); } } } vector< int > ll, rr; { // split nation static long long taken[ MAXN ]; for( int i = 0; i < nation.size(); ++i ){ int owner = nation[ i ]; taken[ owner ] = 0LL; for( int j = 0; j < rO[ owner ].size(); ++j ){ int field = rO[ owner ][ j ]; taken[ owner ] += ftree.gs( field ); if( taken[ owner ] >= P[ owner ] ) break; } if( taken[ owner ] >= P[ owner ] ){ ll.push_back( owner ); } else{ P[ owner ] -= taken[ owner ]; // cancel contribution of [ lb, mid ) from P rr.push_back( owner ); } } } { // cancel contribution of [ lb, mid ) from ftree for( int i = lb; i < mid; ++i ){ if( L[ i ] <= R[ i ] ){ ftree.add( L[ i ], R[ i ] + 1, - W[ i ] ); } else{ ftree.add( 0, R[ i ] + 1, - W[ i ] ); ftree.add( L[ i ], M, - W[ i ] ); } } } parallel_bs( lb, mid, ll ); parallel_bs( mid, rb, rr ); } signed main(){ ios::sync_with_stdio( 0 ); { cin >> N >> M; for( int i = 0; i < M; ++i ){ cin >> O[ i ]; --O[ i ]; rO[ O[ i ] ].push_back( i ); } for( int i = 0; i < N; ++i ){ cin >> P[ i ]; } cin >> K; for( int i = 0; i < K; ++i ){ cin >> L[ i ] >> R[ i ] >> W[ i ]; --L[ i ], --R[ i ]; } } { vector< int > nation( N ); for( int i = 0; i < N; ++i ){ nation[ i ] = i; } parallel_bs( 0, K, nation ); } { for( int i = 0; i < N; ++i ){ if( ans[ i ] == -1 ){ cout << "NIE\n"; } else{ cout << ans[ i ] << "\n"; } } } return 0; }