【记忆化搜索】2017.5.21杂题[排列]题解

题目概述

有两个1~n的排列A,B和两个指针p,q,刚开始p=q=0,可以执行这些操作:
1.p+1
2.q+1
3.p+1且q+1,但要满足A[p+1]!=B[q+1]
问至少多少次操作才能使p=q=n。
100%:1<=n<=1000000

解题报告

ps:据说是吉司机给普及组讲的一道题目,X_X(卒)
一看到题目就想到DP了,f[i][j]表示序列1的指针从n走到i,序列2的指针从n走到j的最优解。那么:
f[i][j]=min(f[i+1][j],f[i][j+1],f[i+1][j+1](a[i]!=b[j]))+1
答案:f[0][0]

不过这个DP无论是时间还是空间都是爆炸的,于是我们需要想其他办法。我们会发现,在遇到最近的a[i+1]=b[j+1]之前,是根本没有必要DP的,只需要两个指针一起跳就行了。但是一旦遇到了a[i+1]=b[j+1],就需要决定先移动i还是先移动j了。

所以我们可以用记忆化搜索(这种转移方程不明确的DP用记忆化搜索会方便很多啦),对于f[x][y],先找到最近的xx和yy使a[xx+1]=b[yy+1](由于是同时跳的,所以必须保证x-y=xx-yy),然后求出f[xx+1][yy]和f[xx][yy+1],从两者间挑出小的来转移即可。

然而怎么找到xx和yy呢?可以用vector乱搞,建2*n个vector,第i个vector储存了相同时差值为i-n(差值可能是负数)的所有序列1的指针位置(比如序列长度为4,a[2]=b[3],那么第2-3+4个vector就储存了2),将所有vector排序过后,每次在第x-y+n个vector中二分查找第一个比x大的位置就是xx。(如果感觉太绕,可以看代码)

示例程序

#include<cstdio>
#include<vector>
#include<map>
#include<algorithm>
using namespace std;
typedef long long LL;
const int maxn=1000000,MOD=999917;

int n,A[maxn+5],B[maxn+5];
vector<int> C[2*maxn+5];
struct data
{
    int i,j;
    data(int a=0,int b=0) {i=a;j=b;}
    bool operator == (const data &a) const {return i==a.i&&j==a.j;}
};
struct Hashmap //map太慢了……
{
    int E,nxt[2*maxn+5],w[2*maxn+5],lnk[MOD];
    data son[2*maxn+5];
    void Add(int x,data y) {son[++E]=y;nxt[E]=lnk[x];lnk[x]=E;}
    int& operator [] (const data &a)
    {
        int Ha=((LL)a.i*n%MOD+a.j)%MOD;
        for (int j=lnk[Ha];j;j=nxt[j])
            if (a==son[j]) return w[j];
        Add(Ha,a);return w[E];
    }
};
Hashmap f;

char readc()
{
    static char buf[100000],*l=buf,*r=buf;
    if (l==r) r=(l=buf)+fread(buf,1,100000,stdin);
    if (l==r) return EOF; else return *(l++);
}
bool Eoln(char ch) {return ch==10||ch==13||ch==EOF;}
int readi(int &x)
{
    int tot=0,f=1;char ch=readc(),lst='+';
    while ('9'<ch||ch<'0') {if (ch==EOF) return EOF;lst=ch;ch=readc();}
    if (lst=='-') f=-f;
    while ('0'<=ch&&ch<='9') tot=tot*10+ch-48,ch=readc();
    x=tot*f;
    return Eoln(ch);
}
int getr(int x) {return x+n;}
int Find(vector<int> &a,int x) //二分查找
{
    int L=0,R=a.size()-1;
    while (L<=R)
    {
        int mid=L+(R-L>>1);
        if (x<a[mid]) R=mid-1; else L=mid+1;
    }
    return L;
}
int DP(int x,int y)
{
    if (x==n) return n-y;
    if (y==n) return n-x;
    if (f[data(x,y)]) return f[data(x,y)]; //记忆化
    int pos=Find(C[getr(x-y)],x); //找x-y差值中第一个比x大的
    if (pos==C[getr(x-y)].size()) return max(n-x,n-y);
    pos=C[getr(x-y)][pos];
    int xx=pos-1,yy=y+pos-1-x; //x走到pos-1,y同步跟上
    return f[data(x,y)]=min(DP(xx+1,yy),DP(xx,yy+1))+1+pos-1-x;
}
int main()
{
    freopen("permu.in","r",stdin);
    freopen("permu.out","w",stdout);
    readi(n);
    for (int i=1;i<=n;i++) {int x;readi(x);A[x]=i;}
    for (int i=1;i<=n;i++) {int x;readi(x);B[x]=i;}
    for (int i=1;i<=n;i++) C[getr(A[i]-B[i])].push_back(A[i]);
    //在差值A[i]-B[i]中储存A[i]
    for (int i=-n;i<=n;i++) sort(C[getr(i)].begin(),C[getr(i)].end());
    //排序以供二分查找
    printf("%d\n",DP(0,0));
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值