其实这道题一看就是A映射到B的最长上升子序列,但是我们考虑如何用线段树来做
首先可以定义 DP 数组:DP [ i ] [ j ] 表示 A 串在 i 位置,B 串在 j 位置的最多的匹配数
那么可以得到方程:
dp [ i - 1 ] [ j - 1 ] + 1 ( i 和 j 可以匹配 )
dp [ i ] [ j ] = dp [ i ] [ j - 1 ] 或 dp [ i - 1 ] [ j ]
但是这样的 DP 方程是很劣的,转移需要 O(N^2)的时间,稳T
考虑如何一优化
原先的 DP 方程似乎没有可以优化的价值了,因为它可能需要 3 种本质不同的式子递推
考虑更改 DP 数组的定义,改为:DP [ i ] [ t ] 表示 A 串在 i 位置且刚好匹配 B 串的 t 位置时所获得的最优的匹配数(其中预处理出每一个 A 中的 i 可以匹配的 B 中的 t 的位置,最多有9个)
那么 dp [ i ] [ t ] 能够被什么递推呢?根据定义,它可以被所有 dp [ i - 1 ] [ 0 ~ t - 1 ] 递推所得,而最多的匹配数就是 0 ~ t 中的最值,想一想,为什么?
因为 i 能够匹配 t 这是一定的了,有一个的贡献,那么对于之前所有的可以推到 dp [ i ] [ t ] 的 dp 数组中取最大的值其实就是在寻求一个最优的合法匹配个数来使得当前的 dp 最优!
快速查询区间最大值是什么干的?线段树啊,其实树状数组也可以的嘛(但是树状数组只能处理前缀和)……
那么这样我们岂不是要建立 N 个线段树?其实不然,记得滚动数组吗?由于我们的 DP 方程只与 i - 1 的所有 dp 解有关,所以可以将 i 那一维压掉!
得到了全新的方程:dp [ t ] = max { dp [ 0 ~ t - 1 ] + 1 } ,然后将当前的 dp [ t ] 在线段树的位置 t
为什么这样是对的呢,线段树上不会访问到其他的诸如 dp [ i - 2 ] [ t ]?
其实不然,因为每一次都会为当前 dp +1,所以线段树上的最值一定全部都是 i - 1 状态下的 dp 解!
然后就完了
几个小技巧:
1 、存储能够与 A 中 i 位置匹配的 B 中的 t 位置时可以用Vector,也可以用邻接链表(博主用的邻接链表)
2 、在线段树上修改的话为了不使当前求得的 dp [ i ] [ t ] 影响后面的 dp [ i ] [ t ' ] ,可以先查询 t 值较大的,再逐步查询最小的,也可以存一个队列,先查询,再逐个修改(博主开的队列)
AC 代码:
/**************************************************************
Problem: 4990
User: jerrywans
Language: C++
Result: Accepted
Time:4236 ms
Memory:130200 kb
****************************************************************/
#include <bits/stdc++.h>
const int N = 1000000 + 5 ;
int head [ N ] , nxt [ N * 10 ] , to [ N * 10 ] , cn ;
int vmax [ N << 3 ] , pos [ N ] , dp [ N ] , a [ N ] , b [ N ] ;
int n ;
void create ( int u , int v ) {
cn ++ ;
to [ cn ] = v ;
nxt [ cn ] = head [ u ] ;
head [ u ] = cn ;
}
std :: queue < int > q ;
void update ( int o ) {
vmax [ o ] = std :: max ( vmax [ o << 1 ] , vmax [ o << 1 | 1 ] ) ;
}
void modify ( int o , int l , int r , int pos , int vl ) {
if ( l == r ) {
vmax [ o ] = vl ;
return ;
}
int mid = l + r >> 1 ;
if ( pos <= mid ) modify ( o << 1 , l , mid , pos , vl ) ;
else modify ( o << 1 | 1 , mid + 1 , r , pos , vl ) ;
update ( o ) ;
}
int query ( int o , int l , int r , int L , int R ) {
if ( L <= l && r <= R ) return vmax [ o ] ;
int mid = l + r >> 1 , cnt = 0 ;
if ( L <= mid ) cnt = std :: max ( cnt , query ( o << 1 , l , mid , L , R ) ) ;
if ( R > mid ) cnt = std :: max ( cnt , query ( o << 1 | 1 , mid + 1 , r , L , R ) ) ;
return cnt ;
}
int main ( ) {
scanf ( "%d" , & n ) ;
for ( int i = 1 ; i <= n ; i ++ ) {
scanf ( "%d" , & a [ i ] ) ;
pos [ a [ i ] ] = i ;
}
for ( int i = 1 ; i <= n ; i ++ ) {
scanf ( "%d" , & b [ i ] ) ;
for ( int j = - 4 ; j <= 4 ; j ++ )
if ( b [ i ] + j >= 1 && b [ i ] + j <= n )
create ( pos [ b [ i ] + j ] , i ) ;
}
for ( int i = 1 ; i <= n ; i ++ ) {
for ( int j = head [ i ] ; j ; j = nxt [ j ] ) {
int v = to [ j ] ;
q . push ( v ) ;
if ( v - 1 > 0 ) dp [ v ] = query ( 1 , 1 , n , 1 , v - 1 ) + 1 ;
}
while ( ! q . empty ( ) ) {
int tmp = q . front ( ) ;
q . pop ( ) ;
modify ( 1 , 1 , n , tmp , dp [ tmp ] ) ;
}
}
printf ( "%d" , query ( 1 , 1 , n , 1 , n ) ) ;
return 0 ;
}