题目大意:
就是给你 n n n个数,和q次询问,每次询问给你一个区间 [ l , r ] [l,r] [l,r],问你把区间里面的数分配成最少多少块,使得块内出现最多次数的数不超过区间长度的一半(除不尽向上取整)
解题思路:
假设一个区间的众数是
x
x
x个,那么不是众数的个数为
r
x
=
(
r
−
l
+
1
)
−
x
;
rx=(r-l+1)-x;
rx=(r−l+1)−x;
1.如果区间的众数没超过一半,那么我们直接划分成一个区间就好了
因为如果众数都没超过一半,那么其他数那铁都超不了一半。
2.如果这个区间的众数超过了一半,那么我们先从
x
x
x个拿出
r
x
+
1
rx+1
rx+1个,去和不是众数的区间进行匹配,组成一个集合,剩下的每一个组成一个集合
a
n
s
=
x
−
(
r
x
+
1
)
+
1
=
2
∗
x
−
(
r
−
l
+
1
)
;
ans = x - (rx + 1) + 1 = 2 * x - (r-l+1);
ans=x−(rx+1)+1=2∗x−(r−l+1);
3.那么问题就变成了求一个区间里面是否存在一个个数超过区间一半的数,如果有返回个数。
解法1:莫队求区间众数
定义一个
c
n
t
cnt
cnt数组统计每个数出现的次数
定义一个
c
n
t
1
cnt1
cnt1数组统计每个数出现次数的次数
在用莫队求的时候我们知道关键是处理好
[
L
,
R
]
−
>
[
L
,
R
+
1
]
或
者
[
L
−
1
,
R
]
[L,R]->[L,R+1]或者[L-1,R]
[L,R]−>[L,R+1]或者[L−1,R]
[
L
,
R
]
−
>
[
L
+
1
,
R
]
或
者
[
L
,
R
−
1
]
[L,R]->[L+1,R]或者[L,R-1]
[L,R]−>[L+1,R]或者[L,R−1]
我们看
c
n
t
1
cnt1
cnt1的定义我们知道如果
c
n
t
1
[
i
]
>
0
cnt1[i] > 0
cnt1[i]>0那么
c
n
t
1
[
j
]
(
j
<
i
)
>
0
cnt1[j](j<i)>0
cnt1[j](j<i)>0
那么对于一个
c
n
t
1
[
i
]
=
=
1
cnt1[i]==1
cnt1[i]==1那么它一定是最高的那个次数
那么我们就可以写莫队了,
但是这道题卡普通莫队要用奇偶优化才能过
//times : 670ms
#include <bits/stdc++.h>
using namespace std;
const int maxn = 3e5 + 10;
struct node {
int l, r, id;
int b;
}q[maxn];
bool cmp1(node x,node y)
{
return x.b^y.b?x.b<y.b:x.b&1?x.r<y.r:x.r>y.r;
}
int arr[maxn], ans[maxn], res = 0;
int cnt[maxn], cnt2[maxn];
inline void Add(int u) {
++ cnt[arr[u]];
++ cnt2[cnt[arr[u]]];
if(cnt2[cnt[arr[u]]] == 1) res = cnt[arr[u]];
}
inline void Sub(int u) {
-- cnt2[cnt[arr[u]]];
if(cnt2[cnt[arr[u]]] == 0) res = cnt[arr[u]]-1;
-- cnt[arr[u]];
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int n,m;
cin >> n >> m;
int block = sqrt(n);
for(int i = 1; i <= n; ++ i) cin >> arr[i];
for(int i = 1; i <= m; ++ i) {
int l, r;
cin >> l >> r;
q[i] = {l,r,i,(l-1)/block+1};
}
sort(q+1,q+1+m,cmp1);
int l = 1, r = 0;
for(int i = 1; i <= m; ++ i) {
while(q[i].l < l) Add(--l);
while(q[i].r > r) Add(++r);
while(q[i].l > l) Sub(l++);
while(q[i].r < r) Sub(r--);
ans[q[i].id] = max(2*res-(r-l+1),1);
}
for(int i = 1; i <= m; ++ i)
cout << ans[i] << "\n";
return 0;
}
解法2:
区域这种区间问题我们很容易想到主席树,
对于区间我们建一颗元素个数和的区间,在主席树上二分,去查询每个区间个数是否超过区间长度,如果超过递归下去求解。
注意:主席树求的并不一定是众数,只有存在超过区间一半的众数返回的才是众数,如果没有返回的不是众数!!
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
#define debug(a) cout << #a << " = " << a << endl;
using namespace std;
typedef long long ll;
const int N = 3E5 + 10;
struct node {
int l, r;
int cou;
}t[N << 5];//求的是这个区间里面又没过超过区间一半的数,如果有返回这个值
int root[N], ind;
int build(int a, int c, int tl, int tr, int p) {
int x = ++ind;
t[x] = t[p]; t[x].cou += c;
if (tl == tr) return x;
int mid = tl + tr >> 1;
if (a <= mid) t[x].l = build(a, c, tl, mid, t[p].l);
else t[x].r = build(a, c, mid + 1, tr, t[p].r);
return x;
}
int ask(int tl, int tr, int p, int x) {
if (tl == tr) return t[x].cou - t[p].cou;
int mid = tl + tr >> 1;
int all = (t[x].cou - t[p].cou) >> 1;
int cou = t[t[x].l].cou - t[t[p].l].cou;
if (cou >= all) return ask(tl, mid, t[p].l, t[x].l);
return ask(mid + 1, tr, t[p].r, t[x].r);
}
int main()
{
int n, m; cin >> n >> m;
rep(i, n) {
int x; scanf("%d", &x);
root[i] = build(x, 1, 1, n, root[i - 1]);
}
while (m--) {
int l, r; scanf("%d %d", &l, &r);
int num = ask(1, n, root[l - 1], root[r]);
int can = (r - l + 2) / 2;
cout << num << endl;
if (num <= can) printf("1\n");
else {
int other = r - l + 1 - num;
int res = num - other;
printf("%d\n", res);
}
}
return 0;
}