题解 CF1354D 【Multiset】

考试拿到题,一看,这不是权值线段树吗?

思路

使用线段树每个节点维护该区间内元素出现次数。

根据题目,对于加入、删除元素,我们可以单点修改(\(+1\)\(-1\)),对于输出,我们可 随便 遍历找一个出现次数为 \(1\) 的元素即可。

代码

具体解释见注释

#include
#define re read()
using namespace std;
const int MAXN=1e6+10;
int n,q,A[MAXN];
struct SEGTREE{
	int sum;//sum 最大为10^6 无需long long(我第一次手残开了,然后爆了空间)
}tr[MAXN<<2];
int read(){//快读
	#define gt getchar()
	#define isdi(a) (a>='0'&&a<='9')
	int x=0,sgn=1;char ch=gt;
	for(;!isdi(ch);ch=gt)if(ch=='-')sgn=-1;
	for(;isdi(ch);ch=gt)x=(x<<1)+(x<<3)+(ch^48);
	return x*sgn;
}
void build(int k,int l,int r){ //建树
	if(l==r){
		tr[k].sum=A[l];
		return;
	}
	int mid=(l+r)>>1;
	build(k<<1,l,mid); build(k<<1|1,mid+1,r);
	tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;
	return;
}
void add(int k,int l,int r,int num){//加入元素
	if(l==r){
		if(l==num)tr[k].sum++;
		return;
	}
	int mid=(l+r)>>1;
	if(num>mid)add(k<<1|1,mid+1,r,num);
	else add(k<<1,l,mid,num);
	tr[k].sum++;
	return;
}
void del(int k,int l,int r,int num){
//删除该区间内第num大的元素
	if(l==r){
		if(tr[k].sum)tr[k].sum--;
		return;
	}
	if(num>tr[k].sum)return;
	int mid=(l+r)>>1;
	if(num>tr[k<<1].sum)del(k<<1|1,mid+1,r,num-tr[k<<1].sum);
//如果num大于左子树元素出现的总次数,则去右子树删第(num-左子树元素出现总次数)大的数
	else if(num<=tr[k<<1].sum)del(k<<1,l,mid,num);
//否则去左子树删除第num大的数
	else return;
	tr[k].sum--;
//该区间内的次数减一
	return;
}
int find(int k,int l,int r){//递归查找
	if(l==r){
		if(tr[k].sum)return l;
		return 0;
	}
	int mid=(l+r)>>1;
	if(tr[k<<1].sum)return find(k<<1,l,mid);
	else return find(k<<1|1,mid+1,r);
	return 0;
//根据题目,随便找一个就行了,找不到就输出0
}
int main (){
	n=re;q=re;
	for(int i=1;i<=n;i++){
		int temp=re;
		A[temp]++;
	}
	build(1,1,n);
	for(int i=1;i<=q;i++){
		int k=re;
		if(k<0)del(1,1,n,-k);
		else add(1,1,n,k);
	}
	printf("%d",find(1,1,n));
	return 0;
}

你可能感兴趣的:(题解 CF1354D 【Multiset】)