题目描述
有两个长度为n的排列(1<=n<=1,000,000),然后你要再次写出一模一样的两个排列,于是你的左手和右手同时拿笔开始写。但是为了锻炼自己的协调能力,你不想左手和右手同时在写一模一样的数,每写一个数你就需要花1ms的时间,那么你要写完这两个序列至少要花多久时间呢?注:每个序列同时只准用一只手写。
DP
要注意到这是两个排列。。
最朴素的dp是设dp[i,j]
如果a[i]!=b[j],肯定贪心
dp[i,j]=dp[i-t,j-t]+t
t越大越好
否则
dp[i,j]=min(dp[i-1,j],dp[i,j-1])
相同的(i,j)对数只有n对。
因此考虑记忆化搜索。
至于如何找到最大的t。
其实就是找到(x,y)使得a[x]=b[y]且y-x=j-i使y最大。
预处理+二分
我在bzoj上咋T了啊
#include<cstdio>
#include<algorithm>
#define min(a,b) (a<b?a:b)
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef pair<int,int> pi;
const int maxn=1000000+10,mx=1000000;
bool bz[maxn];
int a[maxn],b[maxn],c[maxn],wz[maxn],dp[maxn],L[maxn*2],R[maxn*2],id[maxn];
int i,j,k,l,t,n,m;
int read(){
int x=0,f=1;
char ch=getchar();
while (ch<'0'||ch>'9'){
if (ch=='-') f=-1;
ch=getchar();
}
while (ch>='0'&&ch<='9'){
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
int find(int i,int j){
int l=L[j-i+mx]-1,r=R[j-i+mx],mid;
if (!r) return min(i,j);
while (l<r){
mid=(l+r+1)>>1;
if (id[mid]<=j) l=mid;else r=mid-1;
}
if (l<L[j-i+mx]) return min(i,j);else return j-id[l];
}
int dfs(int i,int j){
if (!i||!j) return i+j;
int k,t;
if (a[i]==b[j]){
if (bz[j]) return dp[j];
bz[j]=1;
return dp[j]=min(dfs(i-1,j),dfs(i,j-1))+1;
}
t=find(i,j);
k=dfs(i-t,j-t)+t;
return k;
}
bool cmp(int x,int y){
return x-c[x]<y-c[y]||x-c[x]==y-c[y]&&x<y;
}
int main(){
freopen("dwa107.in","r",stdin);
n=read();
fo(i,1,n) a[i]=read(),wz[a[i]]=i;
fo(i,1,n) b[i]=read(),c[i]=wz[b[i]];
fo(i,1,n) id[i]=i;
sort(id+1,id+n+1,cmp);
fo(i,1,n){
if (i==1||id[i]-c[id[i]]!=id[i-1]-c[id[i-1]]){
L[id[i]-c[id[i]]+mx]=i;
if (i>1) R[id[i-1]-c[id[i-1]]+mx]=i-1;
}
}
R[id[n]-c[id[n]]+mx]=n;
printf("%d\n",dfs(n,n));
}