P3709 大爷的字符串题题解 莫队

让我们吐槽一下,该题的题面不光要求题意转化功底要好,还同样要求你的语文功底要好。

给出原题面:

其实也算不上理解不了,不过出的这样毒瘤的题面但本质却很简单,只能说出题人很会玩。

让我们逐步刨析:

1.这一操作有且仅有一次,因此实质就是固定的常数1。

2.由于我们需要让rp尽可能的大,也就是减的尽可能少,因此这一操作是重点要维护的。

3.x插入S,因此S除了刚开始,永远都不会为空,故与上述的操作一对应。

为了让操作二减的尽可能少,显然要尽可能的让S中的数呈严格上升子序列

通过接下来的模拟帮助大家进一步转化:

假如要查询的区间中的数字为 1 2 2 3 3 3 或者 1 2 2 2 3 3

那么我们刚开始要添加的x一定是最小的,然后下一次再选比x大的数,这样便能满足严格上升。

直到没有比x更大的数再停止,使rp--。

模拟第一组:

一开始为空 rp--

(1)S:[1,2,3]                              剩:2 3 3

(2)rp--  S:[2,3]                         剩:3            

(3)rp--  S:[3]                            结束

最终rp = -3

模拟第二组:

一开始为空 rp--

(1)S:[1,2,3]                              剩:2 2 3

(2)rp--  S:[2,3]                          剩:2

(3)rp--  S:[2]                             结束

最终rp = -3

通过以上的模拟,我们可以得出什么?

好像rp值减去的数等于查询区间中的众数诶?

真的吗?让我们思考一下正确性。

每次配对的数字一定是从小到大的。

并且只要是不同的数,都可以被加入到S中。

完成上述操作之后,一定不能再加入数字了,因此清空S,rp--,把新的x加入S。

可以看出,每一次上述操作后,不同数在区间中剩下来的数量都会-1。

而当这一区间中所有数的数量为0才会结束。

因此rp--的次数不就是该查询区间的众数吗?

思路得出,实践开始。

离线查询考虑莫队,看到a[i]的取值范围如此之大,因此统计出现次数的数组可能会爆。

考虑离散化莫队,按照上述思路再开一个数组t。

莫队核心思路:

cnt[i]表示数i出现的次数,t[i]表示出现i次的数有多少个。

加入一个数时,把now和cnt取个最大值。

删除一个数时,如果有t[cnt]==1 && cnt==now那么now−−。

AC代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+10;
int n,m;
int a[N],cnt[N],ans[N],t[N],b[N];
struct query{
	int l,r,b,id;
}e[N];
bool cmp(query a,query b){
	return (a.b^b.b) ? a.b<b.b : ((a.b&1) ? a.r<b.r : a.r>b.r);
}
inline int read(){int x=0,f=1;char ch=getchar();while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0' && ch<='9'){x=x*10+ch-'0';ch=getchar();}return x*f;}
void write(int x){if(x<0) putchar('-'),x=-x;if(x>9) write(x/10);putchar(x%10+'0');return;} 
signed main(){
	n=read(),m=read();
	int size=sqrt(n);
	for(int i=1;i<=n;i++){
		b[i]=a[i]=read();
	}
	sort(b+1,b+1+n);
	int len=unique(b+1,b+1+n)-b-1;//返回去重后的末尾元素的下一个位置 
	for(int i=1;i<=n;i++){
		a[i]=lower_bound(b+1,b+len+1,a[i])-b;
	}
	for(int i=1;i<=m;i++){
		e[i].l=read(),e[i].r=read();
		e[i].b=e[i].l/size;
		e[i].id=i;
	}
	int l=1,r=0,now=0;
	sort(e+1,e+1+m,cmp);
	for(int i=1;i<=m;i++){
		int x=e[i].l,y=e[i].r;
		while(l<x){
			t[cnt[a[l]]]--;
			if(!t[cnt[a[l]]] && cnt[a[l]]==now) now--;
			t[--cnt[a[l]]]++;	
			l++;
		}
		while(l>x){
			--l;
			t[cnt[a[l]]]--;
			t[++cnt[a[l]]]++;
			now=max(cnt[a[l]],now);
		}
		while(r<y){
			++r;
			t[cnt[a[r]]]--;
			t[++cnt[a[r]]]++;	
			now=max(cnt[a[r]],now);
		}
		while(r>y){
			t[cnt[a[r]]]--;
			if(!t[cnt[a[r]]] && cnt[a[r]]==now) now--;
			t[--cnt[a[r]]]++;
			r--;
		}
		ans[e[i].id]=-now;
	}
	for(int i=1;i<=m;i++){
		write(ans[i]);puts("");
	}
	return 0;
}
                   双倍经验:P1997,不需要离散化,注意a[i]可以取负数。

                            ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​      

        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值