[BZOJ3072]-[Pa2012]Two Cakes-dp有效状态+记搜

本文介绍了一种解决 BZOJ3072 的高效算法,该问题涉及两个长度为 n 的排列,目标是最短时间完成两个排列的书写任务。通过优化动态规划方法,将时间复杂度降低至 Θ(nlog_2n),并提供了实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

说在前面

为什么这么热啊!!!简直受不了啊QAQ
舍不得开空调,感觉浪费电,又没有风扇,要死……


题目

BZOJ3072传送门

题目大意

给出两个长度为 n n 排列,现在需要把这两个排列按顺序抄一遍
你可以左手右手分别写一个排列,但是同一时刻左右手写的数字不能相同(如果相同了,就只能先写其中一个)
每写一个数字消耗 1 单位时间,询问最快多久可以写完
范围: n106 n ≤ 10 6

输入输出格式

输入格式:
第一行一个整数 n n
接下来 n 个整数表示第一个排列
再接下来 n n 个整数表示第二个排列

输出格式:
输出一行一个数字,表示答案


解法

感觉还是比较有意思的
首先 n2 dp d p 是显然的: dp[i][j]min(dp[i1][j],dp[i][j1])+1 d p [ i ] [ j ] ⟵ m i n ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] ) + 1
如果 aibj a i ≠ b j ,那么 dp[i][j]dp[i1][j1]+1 d p [ i ] [ j ] ⟵ d p [ i − 1 ] [ j − 1 ] + 1

考虑对这个 dp d p 进行简化
如果遇见相同的数字,肯定有一个排列延后一步,这个延后我们完全没有必要提前进行(也就是只有恰好 ai=bj a i = b j 的时候才延后),可以证明是不影响答案的

那么,如果 ai=bj a i = b j ,那么就像上面的第一个式子那样转移
不然就 dp[i][j]dp[it][jt]+t d p [ i ] [ j ] ⟵ d p [ i − t ] [ j − t ] + t ,其中 ait+kbjt+k,1kt a i − t + k ≠ b j − t + k , 1 ≤ k ≤ t ait=bjt a i − t = b j − t
可以发现有用的状态就只有 n n 个,也就是当ai=bj时的 dp[i][j] d p [ i ] [ j ]

那么我们可以记忆化有用状态,对于其它的状态我们只需要快速得知 t t 是多少就可以了
那么如何快速计算 t 呢?我们可以采用二分答案!我们可以找出所有的「在两个序列中位置差为 ij i − j 的数字」,然后按照在 a a 中出现的位置排序。因为ait=bjt,那么 it i − t 也就是在「位置差为 ij i − j 的数组」中,第一个小于 i i 的数字

对于每个有用状态,需要用两个转移去计算,每个转移需要log2n的时间去二分 t t ,所以总时间复杂度Θ(nlog2n)
然后这道题就做完了


下面是自带大常数的代码

#include <vector>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;

int N , a[1000005] , b[1000005] , arcB[1000005] , dp[1000005] ;
vector<int> dif[2000005] ;

void read_( int &x ){
    x = 0 ; char ch = getchar() ;
    while( ch < '0' || ch > '9' ) ch = getchar() ;
    while( ch >='0' && ch <='9' ) x = (x<<1) + (x<<3) + ch - '0' , ch = getchar() ;
}

inline int getPre( int dt , int pos ){
    int lf = 0 , rg = dif[dt].size() - 1 , rt = 0 ;
    while( lf <= rg ){
        int mid = ( lf + rg ) >> 1 ;
        if( dif[dt][mid] <= pos ) rt = dif[dt][mid] , lf = mid + 1 ;
        else rg = mid - 1 ;
    } return rt ;
}

int dfs( int i , int j ){
    if( !i || !j ) return i|j ;
    if( a[i] == b[j] ){
        if( !dp[i] ) dp[i] = min( dfs( i - 1 , j ) , dfs( i , j - 1 ) ) + 1 ;
        return dp[i] ;
    } int pre = getPre( i - j + N , i ) ;
    return pre ? dfs( pre , j - i + pre ) + i - pre : max( i , j ) ;
}

int main(){
    scanf( "%d" , &N ) ;
    for( int i = 1 ; i <= N ; i ++ ) read_( a[i] ) ;
    for( int i = 1 ; i <= N ; i ++ )
        read_( b[i] ) , arcB[ b[i] ] = i ;
    for( int i = 1 ; i <= N ; i ++ )
        dif[ i - arcB[a[i]] + N ].push_back( i ) ;
    printf( "%d" , dfs( N , N ) ) ;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值