传送门:第13届ICPC河南站J题
标签:数据结构
题目大意
你有n个甜甜圈,它们被分成了两叠,第一叠有n1个,第二叠有n2个。每个甜甜圈都有一个甜度值,且不存在甜度值相同的两个甜甜圈。现在你可以进行以下操作:
1、如果某一叠顶端的甜甜圈是当前所有甜甜圈中甜度值最大的,你可以直接吃掉它。
2、把任意一叠位于顶端的一个甜甜圈移动到另一叠顶端。
求吃掉所有甜甜圈所需要的最小移动步数。
输入:先输入每叠甜甜圈的个数,再从上到下输入一叠中每个甜甜圈的甜度值
输出:最小移动步数
算法分析
- 理解了题意后我们会注意到:不管甜甜圈如何排列,我们都只能先吃当前甜度值最大的那个,也就是说我们必须先把它移到某一叠的顶端。如此反复操作n次后,所有的甜甜圈都会被吃完,且移动步数毫无疑问是最少的。看到题目说甜度值两两不同,我们就会想到permutation!只要先记录每个甜度值的下标,再把甜度值按降序排列,就可以知道每个甜甜圈的运动轨迹,进而算出移动步数。
- 那么这是一道简单模拟题吗?不尽然。实际操作后我们就会遇到一个困难:无法消除后效性。一个甜甜圈被吃掉后,它应该被删去,也就是说后续不能再移动它。但如果我们用下标之差来算移动步数,就不知道中间有没有、有几个甜甜圈已经被吃了,这样就会导致答案变大,不符合题意。
- 别急,说不定有某种数据结构能解决这个问题。思考一下,如果我们用1表示没吃的甜甜圈,用0表示被吃的甜甜圈,就可以简化需求。那么我们要找的数据结构必须满足:能快速查询数组中任意两下标间有几个“1”,并且能将某个下标的“1”改成“0”。既然支持查询和修改,我们第一时间想到线段树,再看每次修改的变化量为1,那么毫无疑问就是权值线段树!
- 思路已经很清晰了,不过还有最后一个问题:权值线段树维护的是一个数组,但两叠甜甜圈是两个数组。我们可以把两叠的顶端看作“出口”,只要甜度值最大的甜甜圈到了这个“出口”,就可以被吃掉。但是甜甜圈的下标是固定的,那么我们就考虑移动“出口”,每将一个甜甜圈从右边那叠移到左边那叠,左边的出口在数组中向后移动一位,右边的出口在数组中也向后移动一位。没错!我们可以将代表着两叠甜甜圈的数组拼接起来,将两个“出口”合为一个,看成一个可移动的指针。这个指针会按甜度值降序指向每个甜甜圈,而答案就是每次指针移动经过的没被吃的甜甜圈数量之和。
代码实现
#include <iostream>
using namespace std;
int a,b,y,c[100001],index[6000001];
long long count=0LL;
struct Item{
int l,r,count;
}LT[400001]; //线段树开4倍空间
int rule(const void *p,const void *q){
return *((int *)q)-*((int *)p);
}
void build(int x,int l,int r){ //建树
LT[x].l=l;LT[x].r=r; //标准权值线段树不用建树
if(l==r){ //但本题每个点初始值为1
LT[x].count=1;
return ;
}
int mid=(l+r)/2;
build(x<<1,l,mid);
build((x<<1)+1,mid+1,r);
LT[x].count=LT[x<<1].count+LT[(x<<1)+1].count;
}
void update(int x){ //将"1"更新为"0"
if(LT[x].l<=y&&y<=LT[x].r)LT[x].count--;
if(LT[x].l==LT[x].r)return ; //精确到点,不用懒标记
int mid=(LT[x].l+LT[x].r)>>1;
if(y<=mid)update(x<<1);
if(y>mid)update((x<<1)+1);
}
void cal(int x){ //计算区间中"1"的数量
if(a<=LT[x].l&<[x].r<=b){
count+=LT[x].count; //若被查询区间包含则直接加和
return ;
}
int mid=(LT[x].l+LT[x].r)>>1;
if(a<=mid)cal(x<<1); //查询左区间
if(b>mid)cal((x<<1)+1); //查询右区间
}
int main(){
int i,j,x,point,n,n1;
std::ios::sync_with_stdio(false);
std::cin.tie(0);std::cout.tie(0);
cin>>n>>n1;
for(i=n-1;i>=0;i--){cin>>c[i];index[c[i]]=i;} //读取每叠甜甜圈的甜度值和下标
for(i=n;i<n+n1;i++){cin>>c[i];index[c[i]]=i;}
qsort(c,n+n1,sizeof(int),rule); //按甜度值降序排列
point=n; //代表着顶端出口的指针
n=n+n1; //甜甜圈总数
build(1,0,n-1);
for(i=0;i<n-1;i++){
x=index[c[i]];
if(point<x){
a=point;
b=x-1;
}
else if(point>x){
a=x+1;
b=point-1;
}
cal(1); //查询指针移动区间有多少个没被吃的甜甜圈
point=x;//指针移动
y=x;
update(1);//更新数据
}
cout<<count;//输出答案
return 0;
}