(第十四届蓝桥真题) 整数删除(线段树+二分)

 样例输入:

5 3
1 4 2 8 7

样例输出:

17 

分析:这道题我想的比较复杂,不过复杂度还是够用的,我是用线段树+二分来做的。

我们用线段树维护所有位置的最小值,那么我们每次删除一个数之前先求一遍最小值,不妨设最小值为mn,然后从左边开始找第一个值为mn的位置,不妨设位置为pos,我们每次删除一个数,就把这个位置设置为0x3f3f3f3f3f3f3f3f,因为可能爆int,所以设置为longlong里面的较大值。这样我们当前要删除pos位置的数,那么就把pos位置的数设置为0x3f3f3f3f3f3f3f3f,然后从pos-1向前二分寻找第一个位置i使得区间[i,pos-1]的最小值不为0x3f3f3f3f3f3f3f3f,这就是现存的左边相邻的数,同理从pos+1向后二分寻找第一个位置i使得区间[pos+1,i]的最小值不为0x3f3f3f3f3f3f3f3f,这就是现存的右边相邻的数,然后把这两个数都加上mn即可,需要注意的一点就是我们寻找位置时是在线段树内二分,要不然复杂度是n*logn*logn,这样会超时

最后我们直接把那些位置不为0x3f3f3f3f3f3f3f3f的数输出即可。

细节见代码:

#include<iostream>
#include<cmath>
#include<queue>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=2e6+10;
long long mn[N];
int l[N],r[N];
void pushup(int id)
{
	mn[id]=min(mn[id<<1],mn[id<<1|1]);
	return ;
}
void build(int id,int L,int R)
{
	l[id]=L;r[id]=R;
	if(L==R)
	{
		scanf("%lld",&mn[id]);
		return ;
	}
	int mid=L+R>>1;
	build(id<<1,L,mid);
	build(id<<1|1,mid+1,R);
	pushup(id);
}
void update_point(int id,int pos,long long x)
{
	if(l[id]==r[id])
	{
		mn[id]+=x;
		return ;
	}
	int mid=l[id]+r[id]>>1;
	if(pos<=mid) update_point(id<<1,pos,x);
	else update_point(id<<1|1,pos,x);
	pushup(id);
}
long long query_interval(int id,int L,int R)
{
	if(l[id]>=L&&r[id]<=R) return mn[id];
	int mid=l[id]+r[id]>>1;
	long long ans=0x3f3f3f3f3f3f3f3f;
	if(L<=mid) ans=min(ans,query_interval(id<<1,L,R));
	if(mid+1<=R) ans=min(ans,query_interval(id<<1|1,L,R));
	return ans;
}
int queryl_interval(int id,int L,int R,long long val)//从R-->L找第一个值小于val的位置 
{
	if(l[id]>R||r[id]<L) return 0;
	if(l[id]==r[id]&&mn[id]<val) return l[id];
	int mid=l[id]+r[id]>>1;
	int ans=0;
	if(mid+1<=R&&mn[id<<1|1]<val)
		ans=queryl_interval(id<<1|1,L,R,val);
	if(ans) return ans;
	if(L<=mid&&mn[id<<1]<val)
		ans=max(ans,queryl_interval(id<<1,L,R,val));
	return ans;
}
int queryr_interval(int id,int L,int R,long long val)//从L-->R找第一个值小于val的位置
{
	if(l[id]>R||r[id]<L) return 0x3f3f3f3f;
	if(l[id]==r[id]&&mn[id]<val) return l[id];
	int mid=l[id]+r[id]>>1;
	int ans=0x3f3f3f3f;
	if(L<=mid&&mn[id<<1]<val)
		ans=queryr_interval(id<<1,L,R,val);
	if(ans!=0x3f3f3f3f) return ans;
	if(mid+1<=R&&mn[id<<1|1]<val)
		ans=min(ans,queryr_interval(id<<1|1,L,R,val));
	return ans;
}
int main()
{
	int n,k;
	scanf("%d%d",&n,&k);
	build(1,1,n);
	while(k--)
	{
		long long mi=query_interval(1,1,n);
		int l=queryr_interval(1,1,n,mi+1);
		long long val=query_interval(1,l,l);
		update_point(1,l,0x3f3f3f3f3f3f3f3f-val);
		int t=l;
		if(t!=1)//更新左边 
		{
			int pos=queryl_interval(1,1,t-1,0x3f3f3f3f3f3f3f3f);
			if(pos)
				update_point(1,pos,val);
		}
		if(t!=n)//更新右边 
		{
			int pos=queryr_interval(1,t+1,n,0x3f3f3f3f3f3f3f3f);
			if(pos!=0x3f3f3f3f)
				update_point(1,pos,val);
		}
	}
	for(int i=1;i<=n;i++)
	{
		long long val=query_interval(1,i,i);
		if(val!=0x3f3f3f3f3f3f3f3f) printf("%lld ",val);
	}
	return 0;
}

方法二:优先队列加双向链表

我们直接用数组记录每个位置当前的真实值,然后我们用优先队列存储所有的二元组,二元组的第一维就是值,第二维是位置,因为我们不可能每次修改一个值就对应地在优先队列内进行修改,但是我们每次拿出队列首部元素时我们能够通过坐标索引找到当前位置的真实值然后与该二元组的第一维进行比较来判断当前值是否已经发生改变,如果要是值已经发生改变我们直接continue就可以,这样的话就可以使得总体复杂度降至O(nlogn)了。

细节见代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
const int N=5e5+10;
typedef pair<long long,int> PII;
long long a[N];
int l[N],r[N],n,k;
priority_queue<PII,vector<PII>,greater<PII> >q;
void del(int pos)
{
	if(l[pos])
	{
		a[l[pos]]+=a[pos];
		r[l[pos]]=r[pos];
		q.push({a[l[pos]],l[pos]});
	}
	if(r[pos]<=n)
	{
		a[r[pos]]+=a[pos];
		l[r[pos]]=l[pos];
		q.push({a[r[pos]],r[pos]});
	}
	a[pos]=-1;
}
int main()
{
	cin>>n>>k;
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
		q.push({a[i],i});
		l[i]=i-1;
		r[i]=i+1;
	}
	while(k--)
	{
		PII t=q.top();
		q.pop();
		if(a[t.second]!=t.first)//该值已经无效 
		{
			k++;
			continue;
		}
		del(t.second);
	}
	for(int i=1;i<=n;i++)
		if(a[i]!=-1) printf("%lld ",a[i]);
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值