BZOJ 4990 [Usaco2017 Feb] Why Did the Cow Cross the Road II

3 篇文章 0 订阅

其实这道题一看就是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 ;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值