做题笔记,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=r−l+1,且
2
∗
x
>
l
e
n
2*x>len
2∗x>len,那么我们做如下的考虑:
对于类似如下的:
o
o
o
o
o
o
p
p
p
p
(
o
为
众
数
,
p
为
其
他
数
)
oooooo\\pppp\\(o为众数,p为其他数)
oooooopppp(o为众数,p为其他数)
我们分两步来进行
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)
(len−x)段
ps:如果
p
p
p不够的话,还要加上
x
−
2
∗
(
l
e
n
−
x
)
x-2*(len-x)
x−2∗(len−x)段单独的
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=len−x+x−2∗(len−x)=2∗x−len
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
len−x−x/2个多余的
p
p
p
①假如
p
p
p足够多,即
l
e
n
−
x
−
x
/
2
>
=
x
/
2
len-x-x/2>=x/2
len−x−x/2>=x/2即:
2
∗
x
−
l
e
n
<
=
0
2*x-len<=0
2∗x−len<=0时,显然不需要分了,可以全放成一段(此时相当于区间众数没超过一半)
②
p
p
p不足时,最多消去
l
e
n
−
x
−
x
/
2
len-x-x/2
len−x−x/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−(len−x−x/2)=2∗x−len个区间,答案为:
a
n
s
=
2
∗
x
−
l
e
n
ans=2*x-len
ans=2∗x−len
所以,对众数超一半的区间,输出
a
n
s
=
2
∗
x
−
l
e
n
ans=2*x-len
ans=2∗x−len
那么此时只需要求区间众数就可以了
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);
}
}