题目概述
有两个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;
}