GDKOI 2021 提高组 Day2 第二题 群岛
题目大意
n
n
n 个点,每个点
i
i
i 只有连向
i
+
1
i+1
i + 1 和
a
i
a_i
a i 两条出边,
m
m
m 次操作,支持修改
a
i
a_i
a i ,询问点
x
x
x 能到达的编号最小的点。
n
,
m
≤
1
0
5
n,mle10^5
n , m ≤ 1 0 5
题解
显然
a
i
≥
i
a_ige i
a i ≥ i 是没有用的。 考虑某个点
x
x
x 会以何种方式走向最优的点,不失一般性地,一定是先往右走若干格,再通过
a
x
a_x
a x 走向较小的点,然后如此循环重复。 这个过程中会使用多若干组
(
i
,
a
i
)
(i,a_i)
( i , a i ) ,它们能在一次询问中被同时使用到当且仅当存在另一组
(
a
j
,
j
]
(a_j,j]
( a j , j ] 与
(
a
i
,
i
]
(a_i,i]
( a i , i ] 的交不为空,形象的说即为这若干条左开右闭的线段都不能被不理,至少要有另一条与它有重叠。 那么它能走到的位置即为
x
x
x 左边最靠右的一个未被线段覆盖的位置,可以用线段树维护。 具体地,记录每个区间的最小值及最右边的最小值所在的位置,区间合并显然。
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define N 100010
int a[ N] ;
struct {
int r, mi, p;
} f[ N * 4 ] ;
void make ( int v, int l, int r) {
f[ v] . r = r, f[ v] . mi = 0 ;
if ( l == r) return ;
int mid = ( l + r) / 2 ;
make ( v * 2 , l, mid) , make ( v * 2 + 1 , mid + 1 , r) ;
}
int find ( int v, int l, int r, int x, int y) {
if ( x > y) return - 1 ;
if ( l == x && r == y) {
if ( f[ v] . mi == 0 ) return f[ v] . r;
return - 1 ;
}
else {
int mid = ( l + r) / 2 ;
f[ v * 2 ] . p + = f[ v] . p, f[ v * 2 + 1 ] . p + = f[ v] . p;
f[ v * 2 ] . mi + = f[ v] . p, f[ v * 2 + 1 ] . mi + = f[ v] . p;
f[ v] . p = 0 ;
int s;
if ( y <= mid) s = find ( v * 2 , l, mid, x, y) ;
else if ( x > mid) s = find ( v * 2 + 1 , mid + 1 , r, x, y) ;
else {
int t = find ( v * 2 + 1 , mid + 1 , r, mid + 1 , y) ;
if ( t != - 1 ) s = t; else s = find ( v * 2 , l, mid, x, mid) ;
}
f[ v] . mi = min ( f[ v * 2 ] . mi, f[ v * 2 + 1 ] . mi) ;
if ( f[ v * 2 ] . mi < f[ v * 2 + 1 ] . mi) f[ v] . r = f[ v * 2 ] . r; else f[ v] . r = f[ v * 2 + 1 ] . r;
return s;
}
}
void ins ( int v, int l, int r, int x, int y, int c) {
if ( x > y) return ;
if ( l == x && r == y) f[ v] . mi + = c, f[ v] . p + = c;
else {
int mid = ( l + r) / 2 ;
f[ v * 2 ] . p + = f[ v] . p, f[ v * 2 + 1 ] . p + = f[ v] . p;
f[ v * 2 ] . mi + = f[ v] . p, f[ v * 2 + 1 ] . mi + = f[ v] . p;
f[ v] . p = 0 ;
if ( y <= mid) ins ( v * 2 , l, mid, x, y, c) ;
else if ( x > mid) ins ( v * 2 + 1 , mid + 1 , r, x, y, c) ;
else ins ( v * 2 , l, mid, x, mid, c) , ins ( v * 2 + 1 , mid + 1 , r, mid + 1 , y, c) ;
f[ v] . mi = min ( f[ v * 2 ] . mi, f[ v * 2 + 1 ] . mi) ;
if ( f[ v * 2 ] . mi < f[ v * 2 + 1 ] . mi) f[ v] . r = f[ v * 2 ] . r; else f[ v] . r = f[ v * 2 + 1 ] . r;
}
}
int main ( ) {
int n, Q, i, j;
scanf ( "%d%d" , & n, & Q) ;
make ( 1 , 1 , n) ;
for ( i = 1 ; i <= n; i++ ) {
scanf ( "%d" , & a[ i] ) ;
ins ( 1 , 1 , n, a[ i] + 1 , i, 1 ) ;
}
while ( Q-- ) {
int op, x, y;
scanf ( "%d" , & op) ;
if ( op == 1 ) {
scanf ( "%d%d" , & x, & y) ;
ins ( 1 , 1 , n, a[ x] + 1 , x, - 1 ) ;
a[ x] = y;
ins ( 1 , 1 , n, a[ x] + 1 , x, 1 ) ;
}
else {
scanf ( "%d" , & x) ;
int t = find ( 1 , 1 , n, 1 , x) ;
printf ( "%d
" , t == - 1 ? i : t) ;
}
}
return 0 ;
}
自我小结
形象的题意转化并不难,但考场并没有想到。 可以尝试多手玩样例,便会发现很多结论都很显然。