LCIS

本文探讨了一种在有限代价内通过翻转操作优化整数序列,以最大化最长上升子序列长度的算法。采用分治与快排思想,通过离散化处理及标准值分离策略,确保操作代价不超过4*10^6。

Description

  给你一个长度为\(n\)的整数序列,你可以进行的操作是\(rev(l,r)\)表示花费\(r-l+1\)的代价将\([l,r]\)区间内的数字翻转,现在要求在\(4*10^6\)的代价内最大化最终序列的最长上升子序列长度,输出任意一种构造方案

​  数据范围:\(n<=32000\)\(0<=\)序列中的数字\(<=32000\)

  

Solution

  比较直白的想法就是你如果把整个序列排个序自然就满足LIS条件了,那么现在考虑构造一种满足代价要求的方案

  这里有一种很妙的想法,考虑一个类似快排的处理方式:首先离散化(注意每个数都给一个不同的新值)然后我们分治来做,对于当前区间,我们将该区间的离散化后的中位数作为一个标准值,然后剩下来的数可以分为两类,小于标准值的和大于标准值的,我们将小于标准值的全部丢到\(mid\)前面,将大于标准值的全部丢到\(mid\)后面,递归处理完之后前半部分也满足靠后的是大于标准值的,后半部分靠前的是小于标准值的(如果有的话),那么这个时候我们就直接找到前后分界的两个位置然后\(rev\)一下就可以完成大于标准值和小于标准值两类数的分离,然后我们再用同样的方式处理\([l,mid-1]\)\([mid+1,r]\)即可

  至于贡献的话。。好像01序列排序的交换次数(只能交换相邻位置)是有保障的,然后我们在处理的时候两类数的分离其实就是\(01\)分离,所以可以保证到总代价不超

  

  代码大概长这个样子

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=32010;
int a[N],rec[N*14][2],tmp[N];
int n,m,ans,val;
void rev(int l,int r){
    if (l>=r) return;
    rec[++ans][0]=l; rec[ans][1]=r;
    reverse(a+l,a+1+r);
}
void solve1(int l,int r){
    if (l>=r) return;
    int mid=l+r>>1,L,R;
    solve1(l,mid);
    solve1(mid+1,r);
    L=r; R=l;
    for (int i=l;i<=r;++i)
        if (a[i]>=val){L=i; break;}
    for (int i=r;i>=l;--i)
        if (a[i]<=val){R=i; break;}
    rev(L,R);
}
void solve(int l,int r){
    if (l>=r) return;
    int mid=l+r>>1,L,R;
    for (int i=l;i<=r;++i) tmp[i]=a[i];
    sort(tmp+l,tmp+1+r);
    val=tmp[mid];
    for (int i=l;i<=r;++i){
        if (a[i]==val){
            rev(min(i,mid),max(i,mid));
            break;
        }
    }
    solve1(l,mid-1);
    solve1(mid+1,r);
    L=R=mid;
    for (int i=l;i<=r;++i)
        if (a[i]>=val){L=i; break;}
    for (int i=r;i>=l;--i)
        if (a[i]<=val){R=i; break;}
    rev(L,R);
    solve(l,mid-1);
    solve(mid+1,r);
}
bool cmp(int x,int y){return a[x]<a[y];}
void prework(){
    for (int i=1;i<=n;++i) tmp[i]=i;
    sort(tmp+1,tmp+1+n,cmp);
    for (int i=1;i<=n;++i) a[tmp[i]]=i;
}
int main(){
#ifndef ONLINE_JUDGE
    freopen("a.in","r",stdin);
#endif
    scanf("%d",&n);
    for (int i=1;i<=n;++i) scanf("%d",a+i);
    prework();
    solve(1,n);
    printf("%d\n",ans);
    for (int i=1;i<=ans;++i) printf("%d %d\n",rec[i][0],rec[i][1]);
}

转载于:https://www.cnblogs.com/yoyoball/p/10040388.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值