Codeforces Round #807 (Div. 2) E. Mark and Professor Koro(线段树二分)

E. Mark and Professor Koro
题意

给定一个长度为n的数组,有q次更新操作,每次更新会将下标为k的元素的值更新为l,数组的更新是永久的。将数组更新后假设重复做以下操作,求可以得到的最大值。

  • 选择数组中两个相同的数x
  • 将这两个数从数组中删除
  • 将x+1加入到数组中

上述操作不改变原有的数组。

分析

将两个相同的数合并后值加1,可以联想到二进制加法 2 x + 2 x = 2 x + 1 2^x+2^x=2^{x+1} 2x+2x=2x+1,因此问题就转化为将每个数 a [ i ] a[i] a[i]都看作 2 a [ i ] 2^{a[i]} 2a[i],求将这些数相加后得到的二进制数最高位1的位数。将 a [ k ] a[k] a[k]修改为 l l l就转化为从原有的二进制数中减去 2 a [ k ] 2^{a[k]} 2a[k],再加上 2 l 2^l 2l,其中涉及到二进制借位进位。问题经过转化后需要进行的操作有单点修改,区间修改,查询最高位的1,考虑用线段树维护二进制每一位的状态。对于二进制加法,如果要加的位为0,直接加1即可,如果为1则需要找到比当前位更高的位中值为0且位数最低的一位,这就需要在线段树上进行二分,同理二进制减法当前位为0时也需要二分。查询二进制最高位的1同样需要二分,因此本题用到三次线段树二分。在线段树内二分的复杂度为 l o g ( n ) log(n) log(n),总时间复杂度为 O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n))

AC代码

#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long ll;
const int N=2e5+100;
int a[N],b[N];
int n,q,mx,tot;

struct Segment_Tree {
	int tr[N<<2],tag[N<<2];
	void addtag(int p,int l,int r,int d)
	{
		tr[p]=(r-l+1)*d;
		tag[p]=d;
	}
	void pushdown(int p,int l,int r)
	{
		if(tag[p]!=-1)
		{
			int mid=(l+r)>>1;
			addtag(p<<1,l,mid,tag[p]);
			addtag(p<<1|1,mid+1,r,tag[p]);
			tag[p]=-1;
		}
	}
	void build(int p,int l,int r)
	{
		tag[p]=-1;
		if(l==r)
		{
			tr[p]=b[l];
			return ;
		}
		int mid=(l+r)>>1;
		build(p<<1,l,mid);
		build(p<<1|1,mid+1,r);
		tr[p]=tr[p<<1]+tr[p<<1|1];
	}
	void change(int p,int l,int r,int x,int y,int d)
	{
		if(x<=l&&y>=r)
		{
			tr[p]=(r-l+1)*d;
			tag[p]=d;
			return ;
		}
		pushdown(p,l,r);
		int mid=(l+r)>>1;
		if(x<=mid) change(p<<1,l,mid,x,y,d);
		if(y>mid)  change(p<<1|1,mid+1,r,x,y,d);
		tr[p]=tr[p<<1]+tr[p<<1|1];
	}
	int query(int p,int l,int r,int x,int y)
	{
		if(x<=l&&y>=r) return tr[p];
		pushdown(p,l,r);
		int mid=(l+r)>>1;
		int ans=0;
		if(x<=mid) ans+=query(p<<1,l,mid,x,y);
		if(y>mid)  ans+=query(p<<1|1,mid+1,r,x,y);
		return ans;
	}
	int qry0(int p,int l,int r,int x,int z)
	{
		if(l==r) return l;
		pushdown(p,l,r);
		int  mid=(l+r)>>1;
		if(x>mid) return qry0(p<<1|1,mid+1,r,x,z-tr[p<<1]);
		else
		{
			if(tr[p<<1]-z==mid-x) return qry0(p<<1|1,mid+1,r,x,z-tr[p<<1]);
			else return qry0(p<<1,l,mid,x,z);
		}
	}
	int qry1(int p,int l,int r,int x,int z)
	{
		if(l==r) return l;
		pushdown(p,l,r);
		int  mid=(l+r)>>1;
		if(x>mid) return qry1(p<<1|1,mid+1,r,x,z-tr[p<<1]);
		else
		{
			if(tr[p<<1]-z==0) return qry1(p<<1|1,mid+1,r,x,z-tr[p<<1]);
			else return qry1(p<<1,l,mid,x,z);
		}
	}
	int qry(int p,int l,int r,int x)
	{
		if(l==r) return l;
		pushdown(p,l,r);
		int mid=(l+r)>>1;
		if(tr[p<<1]==x) return qry(p<<1,l,mid,x);
		else return qry(p<<1|1,mid+1,r,x-tr[p<<1]);
	}
}seg;

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin>>n>>q;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		b[a[i]]++;
		mx=max(mx,a[i]);
	}
	int sum=0;
	for(int i=1;i<=mx;i++)
	{
		b[i]+=sum;
		sum=b[i]/2;
		b[i]=b[i]%2;
	}
	int tot=mx;
	while(sum)
	{
		b[++tot]=sum%2;
		sum/=2;
	}
	tot=200050;
	seg.build(1,1,tot);
	while(q--)
	{
		int k,l;
		cin>>k>>l;
		if(!seg.query(1,1,tot,l,l)) seg.change(1,1,tot,l,l,1);
		else
		{
			int sum=seg.query(1,1,tot,1,l);
			int r=seg.qry0(1,1,tot,l,sum);
			seg.change(1,1,tot,l,r-1,0);
			seg.change(1,1,tot,r,r,1);
		}
		int x=a[k];
		if(seg.query(1,1,tot,x,x)) seg.change(1,1,tot,x,x,0);
		else
		{
			int sum=seg.query(1,1,tot,1,x);
			int r=seg.qry1(1,1,tot,x,sum);
			seg.change(1,1,tot,x,r-1,1);
			seg.change(1,1,tot,r,r,0);
		}
		a[k]=l;
		cout<<seg.qry(1,1,tot,seg.tr[1])<<endl;
	}
	
	return 0;
}

注意

在线段树外二分的复杂度为 l o g n 2 log_n^2 logn2,总的时间复杂度为 O ( n l o g n 2 ) O(nlog_n^2) O(nlogn2)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值