Codeforces Round #807 (Div. 2) E. Mark and Professor Koro(线段树 + 分治)
E. Mark and Professor Koro
题意 :有一个数列a,定义以下操作 :
找到两个相同的数字 \(x\),从该数列中删除这两个数字,并将 \(x + 1\) 添加进来
进行多次询问,每次给出两个数字 \(x,l\),将 \(a_x\) 替换为 \(l\) ,问 我们经过任意次的操作后能够得到的最大的数字
分析 :
操作和二进制的进位非常相似,所以我们可以考虑拿一个01串p来表示当前可以得到的所有数,\(p_x = 1\) 代表 x 可得到 \(p_x = 0\) 代表 x 不可得到,那么最高位的1所在下标就是要求的最大值。接着考虑替换操作,替换操作可以看成删除一个 \(a_x\) 并添加一个 \(l\)
(以下01串最左边为最低位,与平常习惯不同)
对于添加操作
如果\(p_l = 0\) 那么直接变为1即可
如果\(p_l = 1\) 那么我们就要进行进位,例如
...1111100...在第一个1的位置上加1会变成
...0000010
可以看到当有一连串1时我们需要对一个区间进行修改直到最近的0
对于删除操作
如果\(p_l = 1\) 那么直接变为0即可
如果\(p_l = 0\) 那么我们就要进行借位,例如
...000001...在最左面的0的位置上减1会变成
...111110...
可以看到当有一连串0时我们需要对一个区间进行修改直到最近的1
得到结论 : 我们可以拿线段树来维护这个01串
那么在需要进/借位时如何快速找到离其最进的0/1呢
由于线段树是一个二叉树结构,所以我们可以像二叉查找树那样进行查找(看了很多题解都说这叫二分,但是我觉得不妥,顶多叫分治)
那么查找最近的0/1,最高位的1,我们都可以使用这个方法
那么问题就解决了
ac代码
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <queue>
#include <map>
#include <vector>
#include <stack>
#include <set>
#include <sstream>
#include <fstream>
#include <cmath>
#include <iomanip>
#include <bitset>
#include <unordered_map>
#include <unordered_set>
#define x first
#define y second
#define ios ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define endl '\n'
#define pb push_back
#define all(x) x.begin(),x.end()
#define all1(x) x.begin()+1,x.end()
#define pi 3.14159265358979323846
using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
const int N = 200100,M = 500010,INF = 0x3f3f3f3f,mod = 998244353;
const double INFF = 0x7f7f7f7f7f7f7f7f;
int n,m,k,t;
int a[N],w[N];
struct node
{
int l,r;
int sum,add;
}tr[N << 2];
void pushup(int u)
{
tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}
void pushdown(int u)
{
auto & left = tr[u << 1],& right = tr[u << 1 | 1];
auto & add = tr[u].add;
if(add)
{
left.sum += (left.r - left.l + 1) * add ;
right.sum += (right.r - right.l + 1) * add ;
left.add += add;
right.add += add;
add = 0;
}
}
void build(int u,int l,int r)
{
if(l == r)
{
tr[u] = {l,r,w[l],0};
return ;
}
tr[u] = {l,r,0,0};
int mid = l + r >> 1;
build(u << 1,l,mid),build(u << 1 | 1,mid + 1,r);
pushup(u);
}
void modify(int u,int l,int r,int v)
{
if(tr[u].l >= l && tr[u].r <= r)
{
tr[u].sum += (tr[u].r - tr[u].l + 1 ) * v;
tr[u].add += v;
return;
}
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid) modify(u << 1,l,r,v);
if(r > mid) modify(u << 1 | 1,l,r,v);
pushup(u);
}
int query(int u,int x)
{
if(tr[u].l == x && tr[u].r == x) return tr[u].sum;
else
{
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if(x <= mid) return query(u << 1,x);
else return query(u << 1 | 1,x);
}
}
int query0(int u,int x)// 寻找第x位后第一个0
{
if(tr[u].r < x || tr[u].r - tr[u].l + 1 == tr[u].sum) return -1;
if(tr[u].l == tr[u].r && tr[u].sum == 0) return tr[u].l;
pushdown(u);
int t = query0(u << 1,x);
if(~ t) return t;
return query0(u << 1 | 1,x);
}
int query1(int u,int x)// 寻找第x位后第一个1
{
if(tr[u].r < x || tr[u].sum == 0) return -1;
if(tr[u].r == tr[u].l) return tr[u].l;
pushdown(u);
int t = query1(u << 1,x);
if(~ t) return t;
return query1(u << 1 | 1,x);
}
int find(int u)
{
if(tr[u].l == tr[u].r) return tr[u].l;
else
{
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if(tr[u << 1 | 1].sum) return find(u << 1 | 1);
else return find(u << 1);
}
}
int main()
{
ios;
cin >> n >> m;
for(int i = 1;i <= n;i ++) cin >> a[i],w[a[i]] ++;
for(int i = 0;i < N - 1;i ++)
{
w[i + 1] += w[i] / 2;
w[i] %= 2;
}
build(1,1,N - 1);
while(m --)
{
int k,x;
cin >> k >> x;
if(query(1,a[k]) == 1) modify(1,a[k],a[k],-1);
else
{
int l = a[k] ,r = query1(1,a[k]);
;//r = min(r,N - 1);
modify(1,l,r - 1,1);
modify(1,r,r,-1);
}
a[k] = x;
if(query(1,a[k]) == 0) modify(1,a[k],a[k],1);
else
{
int l = a[k],r = query0(1,a[k]);
// r = min(r,N - 1);
modify(1,l,r - 1,-1);
modify(1,r,r,1);
}
cout << find(1) << endl;
}
return 0;
}