[BZOJ3642][CEOI 2014] Cake 线段树

题意:

Leopold买了n块蛋糕, 这些蛋糕排成一排,从左到右记为1到n,第i块蛋糕最初的美味度为di。
Leopold每次固定最先吃第k块蛋糕,于是位置k就空出来了。之后他吃的每一块蛋糕总是位于某个空的位置旁边,并且是美味度最低的一块。因此在任何时刻,所有空的位置是一段连续的区间。Leopold会装饰自己的蛋糕来增加它的美味度,被加过装饰的蛋糕一定会成为最美味的10个蛋糕之一,任何时候都不存在两块美味度相同的蛋糕。
Leopold想知道在他吃到某块蛋糕b之前需要吃掉多少块蛋糕。

输入:

第一行两个整数n和k,表示蛋糕的数量和Leopold吃的第一块蛋糕的位置。
第二行n个不同的整数d1,d2...,dn,表示第i块蛋糕最初的美味度。
第三行一个整数q,表示操作的数量。
下面q行会包括以下两种操作。
(1)E i e 蛋糕i被装饰成了第e美味的蛋糕(即原来前e-1美味的蛋糕不变,原来第e美味的蛋糕变成了第e+1美味的蛋糕,以此类推)注意每次装饰一定会增加美味度。
(2)F b输出Leopold吃到蛋糕b之前需要吃掉多少块蛋糕。

输出:

对于每个F操作,输出一行一个整数,表示蛋糕的数量。

样例略

思路:

先考了不带修改的情况:

先按照最初的美味值进行编号,放到一棵线段树(当然根据K分成两边放到两个线段树上也可以)。

假设我们需要查询的\(x\)\(k\)的左端,我们可以先查询\([x,k-1]\)这段区间内排名的最小值\(Mi\),分析能吃掉这个排名最小(即美味值最高的蛋糕)的情况是在\(k\)的右边存在的一个蛋糕排名更小或者是右边不存在蛋糕了,因此我们要找到右边区间内,第一个小于当前\(Mi\)的位置。

(不带修改的当然可以一次直接用数组处理所有的蛋糕第几个吃掉,但这对后面的写法没有任何帮助)

接下来考虑修改:

当我们将\(x\)的排名变成\(e\)时,我们可以直接将\(x\)的排名变成当前第一的排名-1(因为这里是按照排名越小美味值越高算的),对于\([1,e-1]\)区间内的蛋糕,将它们的排名再依次进行减1

因为\(e\)的范围是在\([1,10]\)之内的,所以我们只用记录前十的蛋糕是哪一些。

更新排名的时候有一个注意点:

如果\(x\)的原来排名是\(7\),变成了\(3\),那么需要进行移动的排名是\([3,6]\)而不是\([3,10]\),所以要特别判一下\(x\)的排名是不是已经在前十当中;

#include<bits/stdc++.h>
#define M 250005
using namespace std;
int n,K,q,Rank[M],Id[M];
struct node {
    int id,x;
    bool operator<(const node&_)const {
        return x>_.x;
    }
} a[M];
struct Tree {
    struct Node {
        int mi;
    } tree[M<<2];
    void up(int p) {
        tree[p].mi=min(tree[p<<1].mi,tree[p<<1|1].mi);
    }
    void updata(int L,int R,int x,int v,int p) {
        if(L==R) {
            tree[p].mi=v;
            return;
        }
        int mid=(L+R)>>1;
        if(x<=mid)updata(L,mid,x,v,p<<1);
        else updata(mid+1,R,x,v,p<<1|1);
        up(p);
    }
    void build(int l,int r,int p) {
        if(l==r) {
            tree[p].mi=Id[l];
            return;
        }
        int mid=(l+r)>>1;
        build(l,mid,p<<1),build(mid+1,r,p<<1|1);
        up(p);
    }
    int Query(int L,int R,int l,int r,int p) {
        if(L==l&&R==r)return tree[p].mi;
        int mid=(L+R)>>1;
        if(r<=mid)return Query(L,mid,l,r,p<<1);
        else if(l>mid)return Query(mid+1,R,l,r,p<<1|1);
        else return min(Query(L,mid,l,mid,p<<1),Query(mid+1,R,mid+1,r,p<<1|1));
    }
    int find_l(int L,int R,int x,int p) { //需要吃的在右边 ,从左边找
        if(tree[p].mi>x)return L-1;
        if(L==R)return L;
        int mid=(L+R)>>1;
        if(tree[p<<1|1].mi<x)return find_l(mid+1,R,x,p<<1|1);
        else return find_l(L,mid,x,p<<1);
    }
    int find_r(int L,int R,int x,int p) {
        if(tree[p].mi>x)return R+1;
        if(L==R)return L;
        int mid=(L+R)>>1;
        if(tree[p<<1].mi<x)return find_r(L,mid,x,p<<1);
        else return find_r(mid+1,R,x,p<<1|1);
    }

} Tl,Tr;
char s[2];
int main() {
    int now=1;//用来记录当前最小的排名
    scanf("%d%d",&n,&K);
    for(int i=1; i<=n; i++)scanf("%d",&a[i].x),a[i].id=i;
    sort(a+1,a+n+1);
    int tot=0;
    for(int i=1; i<=n; i++)Rank[i]=a[i].id,Id[a[i].id]=i;//记录最初的排名
    if(K!=1)Tl.build(1,K-1,1);
    if(K!=n)Tr.build(K+1,n,1);
    scanf("%d",&q);
    while(q--) {
        scanf("%s",s);
        if(s[0]=='F') {
            int x;
            scanf("%d",&x);
            if(x==K)printf("0\n");
            else if(x>K) {
                int k=Tr.Query(K+1,n,K+1,x,1),l=0;
                if(K!=1)l=K-1-Tl.find_l(1,K-1,k,1);
                printf("%d\n",l+x-K);
            } else {
                int k=Tl.Query(1,K-1,x,K-1,1),r=0;
                if(K!=n)r=Tr.find_r(K+1,n,k,1)-K-1;
                printf("%d\n",r+K-x);
            }
        } else {
            int x,y,p=min(n,10);
            scanf("%d%d",&x,&y);
            for(int i=1; i<=min(n,10); i++)if(Rank[i]==x)p=i;//判断x原来的位置是不是在前十
            for(int i=p-1; i>=y; i--)Rank[i+1]=Rank[i];//更新前十的排名
            Rank[y]=x;
            for(int i=y; i>=1; i--) {
                now--;
                if(Rank[i]>K)Tr.updata(K+1,n,Rank[i],now,1);
                else if(Rank[i]<K)Tl.updata(1,K-1,Rank[i],now,1);
            }
        }
    }
    return 0;
}

转载于:https://www.cnblogs.com/cly1231/p/11213110.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值