[bzoj3072]Two Cakes

题目描述

有两个长度为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));
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值