[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;
}