做题笔记,CF 4.19Div2 D CF1514D Cut and Stick(区间众数:线段树 or 莫队)

做题笔记,CF 4.19Div2 D CF1514D Cut and Stick(区间众数:线段树 or 莫队)

题目大意

给你一个长度为n的序列和q个询问。 ( n , q < = 3 e 5 ) (n,q<=3e5) (n,q<=3e5)
每次询问一个区间 ( l , r ) (l,r) (l,r),问你至少要把区间分成多少段,使得每一段的众数出现次数不超过区间长度的一半(向上取整

做法

考虑一个区间,如何分割比较合理:
1.求区间众数后,假如区间众数小于等于区间长度的一半,那么直接不用分了,输出1
2.假如区间众数出现次数为 x x x,区间长度为 l e n = r − l + 1 len=r-l+1 len=rl+1,且 2 ∗ x > l e n 2*x>len 2x>len,那么我们做如下的考虑:
对于类似如下的:
o o o o o o p p p p ( o 为 众 数 , p 为 其 他 数 ) oooooo\\pppp\\(o为众数,p为其他数) oooooopppp(op)
我们分两步来进行
1.我们希望尽可能用 p p p去消掉过多的 o o o,使之符合题目要求
观察到,一个非众数最多消去两个众数,即 o o p oop oop,所以,如上的分割变为:
o o p o o p o o p p oop\\oop\\oop\\p oopoopoopp
共计 ( l e n − x ) (len-x) (lenx)
ps:如果 p p p不够的话,还要加上 x − 2 ∗ ( l e n − x ) x-2*(len-x) x2(lenx)段单独的 o o o
那么此时任意区间都不能合并,此时答案为最优
a n s = l e n − x + x − 2 ∗ ( l e n − x ) = 2 ∗ x − l e n ans=len-x+x-2*(len-x)=2*x-len ans=lenx+x2(lenx)=2xlen
2.此时仅满足了每一段的众数出现次数不超过区间长度的一半,但区间数很多,我们考虑将其合并(有更多的 p p p供我们操作)
此时含有两个 o o o的区间个数为 x / 2 x/2 x/2(向下取整)
可以观察到,每有一个多出的 p p p,都能使之前的两个区间合并,而 o p op op可以和任何区间组合而符合题意,例如:
o o p + o o p + p = o o o o p p p o o o o p p p + o p = o o o o o p p p p o o o o o o p p p p p + o o o p p + p = o o o o o o o o o p p p p p p p p oop+oop+p=ooooppp\\ ooooppp+op=ooooopppp\\ ooooooppppp+ooopp+p=ooooooooopppppppp oop+oop+p=oooopppooooppp+op=oooooppppooooooppppp+ooopp+p=ooooooooopppppppp
满足题意
所以不妨设 o o o为偶数个:
本该有 x / 2 x/2 x/2段含众数的区间,但是有 l e n − x − x / 2 len-x-x/2 lenxx/2个多余的 p p p
①假如 p p p足够多,即 l e n − x − x / 2 > = x / 2 len-x-x/2>=x/2 lenxx/2>=x/2即: 2 ∗ x − l e n < = 0 2*x-len<=0 2xlen<=0时,显然不需要分了,可以全放成一段(此时相当于区间众数没超过一半)
p p p不足时,最多消去 l e n − x − x / 2 len-x-x/2 lenxx/2个区间,还剩 x / 2 − ( l e n − x − x / 2 ) = 2 ∗ x − l e n x/2-(len-x-x/2)=2*x-len x/2(lenxx/2)=2xlen个区间,答案为:
a n s = 2 ∗ x − l e n ans=2*x-len ans=2xlen
所以,对众数超一半的区间,输出 a n s = 2 ∗ x − l e n ans=2*x-len ans=2xlen
那么此时只需要求区间众数就可以了
3.区间众数的求法一般是莫队 o ( n n ) o(n\sqrt n) o(nn ),但是当只求一个区间内超过一半的众数时,可使用线段树 o ( n l o g 2 n ) o(nlog_2n) o(nlog2n),注意upper_bound 也有 l o g 2 n log_2n log2n的复杂度

代码

1.一个区间内众数超过一半时
使用线段树时,考虑维护区间内出现的众数是几,如果某个区间的众数为 x x x,那么他的左右儿子至少有一个是 x x x。所以考虑使用线段树遍历区间内的众数,对每个数用upper_bound 和lower_bound 求一下出现次数,统计答案即可。

#include<bits/stdc++.h>
using namespace std;
int t[1200005];
int a[300005];
vector<int> v[300005];
int ans;
int n,q;
int len(int l,int r,int x){
	return upper_bound(v[x].begin(),v[x].end(),r)-lower_bound(v[x].begin(),v[x].end(),l);
}
void build(int l,int r,int v){
	int mid=(l+r)/2;
	if(l==r){
		t[v]=a[l];return;
	}
	build(l,mid,2*v);
	build(mid+1,r,2*v+1);
	t[v]=len(l,mid,t[2*v])>len(mid+1,r,t[2*v+1])?t[2*v]:t[2*v+1]; 
}
int query(int l,int r,int v,int ql,int qr){
	int ret=0;
	if(l>=ql&&r<=qr){
		return len(ql,qr,t[v]);
	}
	int mid=(l+r)/2;
	if(ql<=mid) ret=max(ret,query(l,mid,2*v,ql,qr));
	if(qr>=mid+1) ret=max(ret,query(mid+1,r,2*v+1,ql,qr));
	return ret;
}
int main(){
	cin>>n>>q;
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		v[a[i]].push_back(i);	
	}
	build(1,n,1);
	while(q--){
		int l,r;
		scanf("%d%d",&l,&r);
		ans=query(1,n,1,l,r);
		if(2*ans<=r-l+1+1){
			printf("1\n");
		}
		else{
			printf("%d\n",max(1,2*ans-(r-l+1)));
		}
	}
}

2.使用传统莫队,将区间分成 n \sqrt n n
记录数组num[i]表示i出现的次数,sum[i]表示出现i次的数字个数,ans表示当前的众数
对于询问排序后,每次指针移动修改sum[i],num[i],ans即可

#include<bits/stdc++.h>
using namespace std;
int n,m;
int a[300005],pos[300005],ans;
int sum[300005],num[300005];
struct query{
	int l,r,id,ans;
};query q[300005];
bool cmp(query a,query b){
    return pos[a.l]==pos[b.l]?a.r<b.r:pos[a.l]<pos[b.l];
}
bool cmp2(query a,query b){
	return a.id<b.id;
} 
void add(int x){
	sum[num[a[x]]]--;
	num[a[x]]++;sum[num[a[x]]]++;
	while(sum[ans+1]) ans++;
}
void del(int x){
	sum[num[a[x]]]--;
	num[a[x]]--;sum[num[a[x]]]++;
	while(!sum[ans]) ans--;
}
int main(){
	cin>>n>>m;
	int siz=sqrt(n);
	for(int i=1;i<=n;i++){
		pos[i]=i/siz;
		scanf("%d",&a[i]);
	}
	for(int i=1;i<=m;i++){
		scanf("%d%d",&q[i].l,&q[i].r);q[i].id=i;
	}
	sort(q+1,q+m+1,cmp);
	int l=1,r=0;
	ans=0,sum[0]=1,num[0]=1;
	for(int i=1;i<=m;i++){
		int ql=q[i].l,qr=q[i].r;
		while(r<qr) add(++r);
		while(r>qr) del(r--);
		while(l<ql) del(l++);
		while(l>ql) add(--l);
		q[i].ans=max(1,2*ans-(qr-ql+1));
	}
	sort(q+1,q+m+1,cmp2);
	for(int i=1;i<=m;i++){
		printf("%d\n",q[i].ans);
	} 
} 
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值