给定一个长度为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)