51nod 1364 线段树

给出一个1至N的排列,允许你做不超过K次操作,每次操作可以将相邻的两个数交换,问能够得到的字典序最大的排列是什么?
例如:N = 5, {1 2 3 4 5},k = 6,在6次交换后,能够得到的字典序最大的排列为{5 3 1 2 4}。


解题:

System Messag 的题解》

这个题目明显是要贪心的。从前往后每次看看能不能拿一个大的放到当前数字的前面,而且要拿尽可能大的。这样字典序才会变成最大。

原数组设为a[i]

这个过程可以用线段树来维护。先按照原数组建立线段树。树结点中记录最大值,最大值所在位置,当前区间有几个数字没有被删除。

然后从前往后开始,设当前位置为P,剩K步可以拿,那么就要从P后面拿一个a[Q],他要比a[P]大且区间[P,Q]未删除数字个数不超过K+1。可以先找到距离P最远X的且[P,X]里面未删除的数字不超过K+1个。然后查询[P,X]中最大值,如果比a[P]大就可以换了,不然的话就P++;Q放到前面之后就在线段树里面进行删除。

距离P最远X的且[P,X]里面未删除的数字不超过K+1个,这一步可以通过找[0,X]不超过某个数字来做,可以有logn.

以上步骤均可以在 logn以内实现,所以总复杂度是 nlogn。


#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string.h>
#include<algorithm>
using namespace std;
#define ls rt<<1
#define rs rt<<1|1
const int maxn=200000;
//删除
//找区间最大值
//可以分成两步写呀
//线段树的话果然还是想好操作然后再想维护什么比较容易有思路

struct node
{
    int l,r;
    int ma;
    int num;
}T[maxn<<2];

int times;
int here[maxn];
int flag[maxn];
int a[maxn],a2[maxn];


void pushup(int rt)
{
    T[rt].num=T[ls].num+T[rs].num;
    T[rt].ma=max(T[ls].ma,T[rs].ma);
}

void build(int rt,int L,int R)
{
    T[rt].l=L;T[rt].r=R;
    if(L==R)
    {
        T[rt].ma=a[L];
        T[rt].num=1;
        return;
    }
    int mid=(L+R)>>1;
    build(ls,L,mid);
    build(rs,mid+1,R);
    pushup(rt);
}


//查询最大值并删除
void update(int rt,int pos)//标记为0
{
//    cout<<T[rt].l<<" "<<T[rt].r<<" "<<T[rt].ma<<endl;
    if(T[rt].l==T[rt].r)
    {
        T[rt].num=0;
        T[rt].ma=0;
        flag[T[rt].l]=1;
        return;
    }
    int mid=(T[rt].l+T[rt].r)>>1;
    if(pos<=mid) update(ls,pos);
    else update(rs,pos);
    pushup(rt);
}

int query(int rt,int L,int R)
{
//    cout<<rt<<" "<<L<<" "<<R<<endl;
    if(T[rt].l>=L && T[rt].r<=R)
    {
        return T[rt].ma;
    }
    int mid=(T[rt].l+T[rt].r)>>1;
    int tmp=0;
    if(L<=mid) tmp=max(tmp,query(ls,L,R));
    if(R>mid) tmp=max(tmp,query(rs,L,R));//多个地方的 L R的处理
    return tmp;
}

int getpos(int rt,int pos)
{
//    cout<<T[rt].l<<" "<<T[rt].r<<" "<<T[rt].num<<endl;
    if(T[rt].l==T[rt].r)
    {
        return T[rt].l;
    }
     if(T[ls].num>=pos) return getpos(ls,pos);
     else return getpos(rs,pos-T[ls].num);
}


int gettimes(int rt,int L,int R)
{
//    cout<<L<<" "<<R<<" "<<T[rt].num<<endl;
    if(T[rt].l>=L && T[rt].r<=R)
    {
        return T[rt].num;
    }
    int mid=(T[rt].l+T[rt].r)>>1;
    int tmp=0;
    if(L<=mid)
        tmp+=gettimes(ls,L,R);
    if(R>mid)
        tmp+=gettimes(rs,L,R);
    return tmp;
}

int main()
{
    int n,k;
//    freopen("in.txt","r",stdin);
    while(scanf("%d%d",&n,&k)!=EOF)
    {
       for(int i=1;i<=n;i++)
        scanf("%d",&a[i]),
       here[a[i]]=i;
       memset(flag,0,sizeof(flag));
       build(1,1,n);
       int p=0;
       for(int i=1;i<=n;i++)
       {
           if(k==0) break;
           p++;
           int pos=getpos(1,k+1);
           int tmp=query(1,1,pos);
//           cout<<"pos "<< pos<<" ma"<<tmp<<endl;
           update(1,here[tmp]);
           k-=gettimes(1,1,here[tmp]);
           a2[p]=tmp;
//           cout<<k<<" k"<<endl;
       }
//       cout<<k<<endl;
       for(int i=1;i<=n;i++)
       {
           if(flag[i]==0)
           a2[++p]=a[i];
       }
//       cout<<"--------------------------------"<<endl;
       for(int i=1;i<=n;i++)
        cout<<a2[i]<<endl;

    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值