【牛客】13thICPC-J-甜甜圈 题解

文章介绍了如何使用线段树数据结构解决一个关于甜甜圈的优化问题。给定两叠甜甜圈,每叠甜甜圈的顶部甜甜圈会被优先吃掉,目标是最小化移动甜甜圈到顶部的次数。通过将甜甜圈的甜度值排序并结合线段树,可以计算出最小移动步数。代码实现中展示了线段树的构建和更新过程。
摘要由CSDN通过智能技术生成

传送门:第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&&LT[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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值