洛谷P3939 数颜色【主席树 or 莫队】

题目简述

小 C 把她标号从1到n的n只兔子排成长长的一排,来给他们喂胡萝卜吃。 第 i 只兔子的颜色是 a_i
小 C 想知道在区间 [l_j,r_j] 里有多少只颜色为 c_j的兔子。
有时编号为 x_j 和 x_j+1的两只兔子会交换位置

输入格式

输入第 1 行两个正整数 n,m。
输入第 2 行 n 个正整数,第 i 个数表示第 i 只兔子的颜色 a_i
输入接下来 m 行,每行为以下两种中的一种:
“ 1   l j   r j   c j “1\ l_j\ r_j\ c_j 1 lj rj cj ” :询问在区间 [ l j , r j ] [l_j,r_j] [lj,rj]里有多少只颜色为 c j c_j cj的兔子
“ 2   x j ” “2\ x_j” 2 xj x j x_j xj x j + 1 x_j+1 xj+1两只兔子交换了位置。

n , m ≤ 3 ∗ 1 0 5 n,m \leq 3*10^5 n,m3105

输出格式

输出到标准输出中。
对于每个 1 操作,输出一行一个正整数,表示你对于这个询问的答案。

题目分析

首先说一下官方正解是vector存每个颜色出现位置,然后每次询问在对应vector里二分就行
但本人数据结构学傻了,第一眼带修改莫队,然后卡70pts,之后就怒写主席树,过了
所以这里贴主席树和莫队写法

按套路建主席树
每次交换x和x+1只用修改第x个线段树就行了

// 主席树

const int maxn=400010;
int n,m;
int a[maxn],b[maxn];
int pos[maxn],cnt;
int rt[maxn<<6],ch[maxn<<6][2],sz;
int size[maxn<<6];
int rem[maxn];

int update(int pre,int ll,int rr,int x,int w)
{
	int tt=++sz;
	size[tt]=size[pre]+w;
	ch[tt][0]=ch[pre][0]; ch[tt][1]=ch[pre][1];
	
	int mid=ll+rr>>1;
	if(ll<rr)
	{
		if(x<=mid) ch[tt][0]=update(ch[pre][0],ll,mid,x,w);
		else ch[tt][1]=update(ch[pre][1],mid+1,rr,x,w);
	}
	return tt;
}

int query(int lft,int rht,int ll,int rr,int c)
{
	if(ll==rr) return size[rht]-size[lft];
	int mid=ll+rr>>1;
	if(c<=mid) return query(ch[lft][0],ch[rht][0],ll,mid,c);
	else return query(ch[lft][1],ch[rht][1],mid+1,rr,c);
}

int main()
{
	n=read(); m=read();
	for(int i=1;i<=n;++i)
	a[i]=b[i]=read();
	
	sort(b+1,b+1+n);
	for(int i=1;i<=n;++i)
	if(i==1||b[i]!=b[i-1])
	pos[++cnt]=b[i];
	
	for(int i=1;i<=n;++i)
	{
		rem[a[i]]=1;
		a[i]=lower_bound(pos+1,pos+1+cnt,a[i])-pos;
		
		rt[i]=update(rt[i-1],1,cnt,a[i],1);
	}
	
	
	for(int i=1;i<=m;++i)
	{
		int opt=read();
		if(opt==1)
		{
			int ll=read(),rr=read(),c=read();
			if(!rem[c])
			{
				printf("0\n");
				continue;
			}
			
			c=lower_bound(pos+1,pos+1+cnt,c)-pos;
			printf("%d\n",query(rt[ll-1],rt[rr],1,cnt,c));
		}
		else if(opt==2)
		{
			int loc=read();
			
			rt[loc]=update(rt[loc],1,cnt,a[loc],-1); // 删掉原来的数
			rt[loc]=update(rt[loc],1,cnt,a[loc+1],1); // 加上下一位的数
			
			swap(a[loc],a[loc+1]);
		}
	}
	
	return 0;
}
// 带修改莫队

const int maxn=300010;
int n,m,sz;
struct Query{ int ll,rr,c,pre,id;}q[maxn];
struct Change{ int pos;}change[maxn];
int cntq,cntc;
int a[maxn],cnt[maxn];
int ans[maxn];

bool cmp(Query a,Query b)
{
	if(a.ll/sz!=b.ll/sz) return a.ll/sz<b.ll/sz;
    if(a.rr/sz!=b.rr/sz) 
    {
    	if((a.ll/sz)&1) return a.rr<b.rr;
    	else return a.rr>b.rr;
	}
    return a.pre<b.pre;
}

void add(int idx){ cnt[a[idx]]++;}
void del(int idx){ cnt[a[idx]]--;}
void modify(int idx, Query q)
{
	if(idx==q.ll-1)
	{
		--cnt[a[idx+1]];
		++cnt[a[idx]];
	}
	else if(idx==q.rr)
	{
		--cnt[a[idx]];
		++cnt[a[idx+1]];
	}
	swap(a[idx],a[idx+1]);
}

int main()
{
	n=read(); m=read();
	for(int i=1;i<=n;++i)
	a[i]=read();
	
	for(int i=1;i<=m;++i)
	{
		int opt=read();
		if(opt==1)
		{
			q[++cntq].ll=read();
			q[cntq].rr=read();
			q[cntq].c=read();
			
			q[cntq].pre=cntc;
			q[cntq].id=cntq;
		}
		else if(opt==2){
			change[++cntc].pos=read();
		}
	}
	
	sz=pow(n,2.0/3);
	sort(q+1,q+1+cntq,cmp);
	
	int L=1,R=0, cur=0;
	for(int i=1;i<=cntq;++i)
	{
		while(R<q[i].rr) add(++R);
		while(R>q[i].rr) del(R--);
		while(L<q[i].ll) del(L++);
		while(L>q[i].ll) add(--L);
		
		while(cur<q[i].pre) modify(change[++cur].pos, q[i]);
		while(cur>q[i].pre) modify(change[cur--].pos, q[i]);
		
		ans[q[i].id]=cnt[q[i].c];
	}
	
	for(int i=1;i<=cntq;++i)
	printf("%d\n",ans[i]);
	
	return 0;
}
洛谷P1168题目是关于中位线段树解法的问题。中位线段树解法可以通过维护两个堆来实现。一个是大根堆,一个是小根堆。每次插入元素时,根据一定的规则来维护这两个堆,使得大根堆的个在一定情况下比小根堆多1或者相等。大根堆的最后一个元素即为中位。具体的规则如下: 1. 如果大根堆和小根堆的个相等,下一次插入的元素一定插入到大根堆。此时判断小根堆的堆顶是否大于当前元素x,如果是,则将小根堆的堆顶元素插入到大根堆,然后将x压入小根堆;否则直接将x压入大根堆。 2. 如果大根堆和小根堆的个不相等,按照类似的规则进行操作。 通过以上规则,可以实现在每次插入元素时,维护两个堆的平衡,并且保证大根堆的最后一个元素即为中位。 这种解法的时间复杂度为O(logN),其中N为序列的长度。 #### 引用[.reference_title] - *1* *2* [中位(洛谷p1168)(堆/树状组+二分/线段树+二分)](https://blog.csdn.net/qq_45604735/article/details/114382762)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [洛谷 P1168 中位(权值线段树,离散化)](https://blog.csdn.net/qq_38232157/article/details/127594230)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值