[DP]permu

贴一发宽神题解:
66ccff 的小型脑洞日记
By 66ccff On Nov 1st 2014
感觉实在是无聊了,不妨写一些自己的脑洞口巴,虽然自己也不能保证这个的正确性(萌
萌哒~)。

题目大意

有两个 1~n 的排列 A,B。有两个变量 p,q,其初值均为 0.一次操作指下面 3 条之一:
1.p++,当且仅当 p < n.
2.q++,当且仅当 q < n.
3. p++且 q++,当且仅当 p < n,q < n 且 A[p+1]≠B[q+1].
求使得 p=n 且 q=n 的最少操作次数。
脑洞开始啦! ~

数据范围

30%:n<=100
60%:n<=80000
100%:n<=500000

题解

似乎这个数据范围在提示我往某个神奇的 greed 上面想,不过最近实在是被 Mr.Greed
整怕了,所以我一开始并不急着搞出来,于是先目测出一个 30%暴力如下:
设 f[i][j]表示 p=i 且 q=j 时所需要的最少操作次数,显然有:
F[n][n]=0;
F[i][j]=min{f[i+1][j]+1,f[i][j+1]+1,f[i+1][j+1]+1( 当且仅当 a[i+1]≠b[j+1]) }
然后我们取 f[0][0]作为答案。
这个暴力是显然的,我在想,能不能通过这个暴力证明某个贪心性质呢,然后我开始忙
活了,半天没有结果。
这纯属活该,因为我一直没有看到一个重要的条件: a,b 均为 1~n 的一个排列。而上述
方法却没有针对这个条件作出任何改进。
然后我们就开始研究排列的性质。嘛,性质,一个神奇的东西,你也许能够发现很多性
质,但是不知道哪些能够用。寻找性质似乎是一个综合的问题而不是一个分析的问题,对于
我这种喜欢用分析法写题目的人来说这东西很让人头大啊, QAQ。
最后得到了一个显然的性质(真的很显然):对于一个排列,若存在一组(i,j)使得
a[i+1]=b[j+1],则:对于任意 k!=j,a[i+1]!=b[k+1],对于任意 k!=i,a[k+1]!=b[j+1]。用浅显易懂的话
来说,因为这是个排列,所以每个数列里面的元素都是不同滴~可以把这个结论再加强一点:
对于两个 1~n 的排列,对于任何一个 0<=i < n,有且仅有 1 个 0<=j< n 使得 A[i+1]=B[j+1]( *)。
这个性质有什么用呢,我真的不知道,于是我开始从状态转移的过程上入手,试图用一
个炫酷的方法优化,不过我知道的方法只有矩阵加速,而这在这道题里面显然是不行的。
就在这时,我注意到了一点。很重要!
是人都知道,要使得 F[i][j]取最小,如果 A[i+1]! =B[j+1],则肯定有 F[i][j]=1+F[i+1][j+1],
意思就是能往 F[i+1][j+1]转移就这么转移,这个贪心显然是无比正确的。
所以不考虑 A[i+1]!=B[j+1]这个性质,肯定有 F[i][j]=F[i+1][j+1]+1。
你猜怎么着?我当时脑洞了一下,发现你可以把 F[i+1][0…n]这个数组中的 F[i+1][0]去掉,
然后将 F[i+1][1…n]整体往前移,然后再把 F[i][n]=F[i+1][n],即把新数组的末尾的 F[i+1][n-1]
(前移之前的 F[i+1][n])复制一次以后添加到 F[i+1][n],在此基础上你在给整个数组搞一个
+1 的全局标记, 然后你就得到了一个 F[i][0…n]数组!
呵呵了吧,这个操作是不是很熟悉?没错,你把 F[i+1][0…n]依次存进一个队列里面,然
后把队头(即 F[i+1][0])删除,然后把队尾复制一遍以后再从尾部插进去(比方说从下面插
进去什么的…好萌! >_<),再给队列来个全局+1 标记,现在这个队列里面从头到尾就是
F[i][0…n]的值!
是不是漏掉了什么?没错,我们刚刚的分析无视了 A[i+1]!=B[j+1]这个前提条件,但是如
果考虑这个前提条件呢?又会怎样呢?
我们刚刚得到了一个神奇的从 F[i+1][0…n]递推至 F[i][0…n]的方式,考虑其中的某个 F[i][j],
因为它满足 A[i+1]=B[j+1],于是它的值不能从 F[i+1][j+1]直接递推而来,然而使用刚刚的队列
方法以后,它里面储存的值(如果考虑给整个队列贴的全局+1 标记的话)就是 F[i+1][j+1]+1
的值,这显然是不合法的。
于是“ 大家注意了”, 我们得对于这种 F[i][j]做出特别处理。因为对于这个不能正常转移
的 F[i][j],还是有 F[i][j]=1+min{F[i+1][j],F[i][j+1]},于是:
F[i+1][j]=F[i][j-1]-1; ( 1)
F[i][j]=min{F[i][j-1],F[i][j+1]+1} ( 2)
也许有人会质疑( 1)式的正确性:有可能 F[i][j-1]也是一组不能正常转移的 i 和 j 啊,
这样它就不会等于 F[i+1][j]+1,那么( 1)式就是错误的,( 2)式也就无从谈起啊!
这时为了说服我自己,我拿出了( *) 性质,没错,就是上一页那个我特别标了红色*
号的那个说了以后会用到的性质。因为对于我们当前要转移的 i,已经确定了 j 是不能正常
转移的,那么就没有其他的 k,使得 F[i][k]不能正常转移。所以 F[i][j-1]肯定是能够正常转移
的!于是我们就这么证明了( 1)式的正确性,( 2)式也就水到渠成了。
好啦,我们就可以进行如下操作: 按照之前的队列方法转移以后,假设 A[i+1]=B[j+1],
那么 F[i][j]就是一个不能正常转移的状态,找到这个队列当中从头往尾的第 j+1 个元素,它
就是 F[i][j](因为第一个元素是 F[i][0]),我们规定它等于 min{F[i][j-1],F[i][j+1]+1},即 min{这
个队列里面的前一个元素(若这个 j 是队头,则为刚刚从队头丢弃的那个元素),这个队列
里面的后一个元素+1}.
因为这个队列里面装载的始终是 F[i][0…n]的 n+1 个元素,所以这样的元素始终是能找到
的。
我们来算一下复杂度吧!
一开始,对于任意的 i,有 F[n][i]=n-i,把这 n+1 个元素推进队列里面, 需 O(n)的时间。
然后每次从 F[i+1][0…n]转移到 F[i][0…n],我们需要在当前队列里面插入一个元素,删除
一个元素,然后特殊处理若干个元素,但是!由性质*,我们知道每次转移到 F[i][0…n]有且
仅有一个 j 使得 F[i][j]无法正常转移,所以每次转移我们只要特别处理一个元素就可以啦!
因此,每次从 F[i+1][0…n]到 F[i][0…n]的转移是 O(1)的!
因为一共转移 n 次,所以转移需要 O(n)时间,总的复杂度就是 O(n)。最后我可以得到
一个队列,储存 F[0][0…n]的值,其队头就是 F[0][0]=ANS。我们转移了 n 次,所以获得了 n
个全局+1 标记,把这个值+n 以后输出,就是这个算法的输出啦!
最后没想到是这样的结局,贪心没想到,反而脑洞了一个鬼畜到爆炸的 DP,最为猎奇
的是,这个 Dp 一共有 O(n2)个状态,复杂度却是 O(n)的,为什么会出现这种平均转移复杂
度 O(1/n)的爆炸的情况呢,因为我们用 O(1)的时间转移了从 F[i][0…n]共计 n+1 个状态!这种
鬼畜的搞法我到现在还不敢相信其存在呢。

代码

快来膜zyj’code:

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;

const int N = 1000100;
int a[N]; int b[N]; int pos[N];
int q[N<<1]; int n;

inline int read()
{
    int x=0,t=1; char ch=getchar();
    while(ch<'0' || ch>'9'){
        if(ch=='-') t=-1;
        ch=getchar();
    }
    while(ch>='0' && ch<='9'){
        x=x*10+ch-'0';
        ch=getchar();
    }
    return t*x;
}

int main()
{
    freopen("permu.in","r",stdin);
    freopen("permu.out","w",stdout);
    n=read();
    for(int i=1;i<=n;i++) a[i]=read();
    for(int i=1;i<=n;i++) b[i]=read();
    for(int i=1;i<=n;i++) pos[b[i]]=i;
    for(int i=0;i<=n;i++) q[i]=n-i;
    int head=0,tail=n;
    for(int i=n;i;i--){
        int t=pos[a[i]]; head++;
        q[head+t-1]=min(q[head+t-2],q[head+t]+1);
        q[++tail]=0;
    }
    printf("%d",n+q[head]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值