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;
}

posted @ 2022-07-17 21:09  notyour_young  阅读(57)  评论(0编辑  收藏  举报