A - 大佬集中营
题目大意
给定一个序列,每次询问某个区间中的数,能分成最少多少个集合,每个集合中出现次数最多的次数不能超过集合大小的一半(上取整)。而且每个集合的大小尽可能大。
题解
不难发现,一个区间里的所以数,一定都是会被分配完的,只有因为一个数的集合也是满足题意的。
接下来,最关键的一步,就是发现答案与区间众数的关系:
- 如果该区间的众数,都没有超过一半,那很显然,只需要分成一组
- 当其众数超过一半了,则不妨假设众数为k,答案就是 2 × k − ( r − l + 1 ) 2 \times k - (r - l + 1) 2×k−(r−l+1)
有了这个结论,就非常简单了,因为并没有修改操作,是静态的,那样每一个位置建一棵权值线段树,维护每个数出现次数的前缀和。
显然,这样的空间是无法接受的,于是可持久化一下,变成主席树就可以了。
至于如何寻找众数呢?
其实主席树也做不到,但是要找超过区间长度一半的众数还是可以找到的,因为它是唯一的。
每次在左右儿子中,寻找一个出现次数超过一半的儿子往下走,如果两个儿子都没有超过一半,则说明没有符合要求的众数,即答案为1。
时间复杂度
主席树每次询问的时间复杂的是 O ( log n ) O(\log n) O(logn),整体的时间复杂度就是 O ( n × log n ) O(n\times\log n ) O(n×logn),只不过常数有点大。
Tag
可持久化
权值线段树
code
//#pragma GCC optimize (2)
//#pragma G++ optimize (2)
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <iostream>
#define G getchar
#define ls(x) tr[x].l
#define rs(x) tr[x].r
#define s(x) tr[x].s
using namespace std;
int read()
{
char ch;
for(ch = G();(ch < '0' || ch > '9') && ch != '-';ch = G());
int n = 0 , w;
if (ch == '-')
{
w = -1;
ch = G();
} else w = 1;
for(;'0' <= ch && ch <= '9';ch = G())n = (n<<1)+(n<<3)+ch-48;
return n * w;
}
const int N = 300005;
int n , m , t , l , r , tot , opv;
int root[N];
struct node
{
int l , r , s;
}tr[N*30];
inline void updata (int x)
{
s(x) = s(ls(x)) + s(rs(x));
}
void ins (int x , int xx , int l , int r)
{
if (l == r)
{
s(xx) = s(x) + 1;
return;
}
int m = (l + r) >> 1;
if (opv <= m)
{
rs(xx) = rs(x);
ls(xx) = ++ tot;
ins(ls(x) , ls(xx) , l , m);
}
else
{
rs(xx) = ++ tot;
ls(xx) = ls(x);
ins(rs(x) , rs(xx) , m + 1 , r);
}
updata(xx);
}
int find (int x , int xx , int l , int r)
{
if (s(xx) - s(x) <= opv) return 0;
if (l == r) return s(xx) - s(x);
int m = (l + r) >> 1;
if (s(ls(xx)) - s(ls(x)) > s(rs(xx)) - s(rs(x)))
return find(ls(x) , ls(xx) , l ,m);
else return find(rs(x) , rs(xx) , m + 1 , r);
}
int main()
{
freopen("a.in","r",stdin);
//freopen("a.out","w",stdout);
n = read(); m = read();
for (int i = 1; i <= n; ++i)
{
opv = read();
root[i] = ++ tot;
ins(root[i-1] , root[i] , 1 , n);
}
for (int i = 0; i < m; ++i)
{
l = read(); r = read();
opv = ((r - l) >> 1) + 1;
t = find(root[l -1] , root[r] , 1 , n);
if (t == 0) printf("1\n");
else printf("%d\n", 2 * t - (r - l + 1));
}
}