解释:
第一ms:1 3
第二ms:2 X
第三ms:3 2
第四ms:X 1
X表示不写。
、、claris推荐的动规题果然不是我等凡人可以挑战的。
很明显可以想到O(n^2)的动规思路:
dp[i][j]=第一列数写到第i个,第二列数写到第j个的最少时间
转移:a[i]==b[j]时 dp[i][j]=min(dp[i+1][j],dp[i][j+1]);
a[i]!=b[j]时 dp[i][j]=dp[i+1][j+1];
然后判断下是否是边界就好了。
在此基础上我们做一定的优化。
1、由于是n个数的排列(之前没注意到这一点所以怎么都想不出来),所以a[i]==b[j]的情况最多只有n种
2、a[i]!=b[j]的情况我们就是可以无脑往后走,直到走到a[i+k]==b[j+k]的时候停止,所以我们可以想个办法直接走到i+k这个位置。O(p)(p是多少后面有说)
所以我们都只用考虑a[i]==b[j]的情况(不是该情况的可以用O(p)的效率转移到这里),一共就是n种状态,O(p)效率转移,
总效率为O(n*p);
问题就变成了我们如何在(i,j)状态下转移到(x,y)(x=i+k,y=j+k)的状态下,我们可以知道,i-j==x-y,所以我们就根据i-j这个位置的差值进行二分查找,找到i后面第一个位置Pos,满足a[Pos]==b[Pos1]&&Pos-Pos1==i-j。
就是对于a数组中每个位置,记录下与b数组中该数所在位置的差值,然后对于每个差值,我们把位置从小到大记录下来,就可以对于同一个差值里二分查找,找到接下来的第一个位置。(感觉语文水平受到了制约,不理解的看代码吧)
刚开始的时候我是多插入了2*n个数,然后排序了下,发现会T,然后参考了claris大佬的代码,终于ac。
#include
#include
#include
#define N 1000005
using namespace std;
int mp[N],a[N],b[N],p[N],n,t,l,r,mid,s[2*N],e[2*N],now,tmp,q[N*2],nxt[N*2],fir[N*2],m;
int find(int l,int r,int x){
while(lx)
return r;else return r+1;
}
int dfs(int x,int y){
if(a[x]==b[y]){
if(mp[x])return mp[x];
t=min(dfs(x+1,y),dfs(x,y+1))+1;
mp[x]=t;
return t;
}else{
int P=find(s[x-y+n],e[x-y+n],x);
if(P==e[x-y+n]+1){
t=max(n-x+1,n-y+1);
return t;
}
t=dfs(q[P],q[P]-x+y)+(q[P]-x);
return t;
}
}
inline char nc(){
static char buf[100000],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline int _read(){
char ch=nc();int sum=0;
while(!(ch>='0'&&ch<='9'))ch=nc();
while(ch>='0'&&ch<='9')sum=sum*10+ch-48,ch=nc();
return sum;
}
void add(int l,int r){nxt[r]=fir[l];fir[l]=r;}
int main(){
freopen("1.in","r",stdin);
freopen("1.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(),p[b[i]]=i;
for(int i=n;i;i--) add(i-p[a[i]]+n,i);
int j;
for(int i=1;i