CF1514D Cut and Stick 题解
题目传送门
大致题意:
给出长度为 \(n\) 的序列 \(A\),同时给出 \(m\) 次询问至少要将区间 \([l,r]\) 分成多少段,才能使每段中没有数的出现次数 \(> \lceil \frac{len}{2} \rceil\)。
分析:
首先,如果连该区间的众数的出现次数都 \(\le \lceil \frac{len}{2} \rceil\),那么只用分 \(1\) 段就行了。
对于剩下的情况,我们就要想办法将非众数与众数组合,并且用尽量少的非众数去搭配尽量多的众数组成一段,然后剩下的众数就只能一一分段了。
很容易发现,此时最优的策略是:选择 \(x - 1\) 个非众数与 \(x\) 个众数组队。
画个图就懂了:
如图,蓝色框起来的部分表示分成一段,这时是最优策略,即划分段数最小。
由图可知:设众数有 \(x\) 个,则非众数有 \(len - x\) 个,其中 \(len\) 为区间长度。
此时的答案为 \(x - (len - x + 1) + 1 = 2 x - len\)。
所以问题就转化成了:维护一个序列 \(A\),静态求区间众数个数。
这个问题可以用可持久化线段树(主席树)来做(其实就是主席树的模板了)。
\(\texttt{Code}\):
#include <iostream>
using namespace std;
const int N = 500010;
int n, m;
struct node{
int ls, rs;
int cnt;
}tr[N << 5];
int root[N], idx;
int insert(int p, int l, int r, int pos) {
int q = ++idx;
tr[q] = tr[p];
tr[q].cnt++;
if(l == r) return q;
int mid = l + r >> 1;
if(pos <= mid) tr[q].ls = insert(tr[p].ls, l, mid, pos);
else tr[q].rs = insert(tr[p].rs, mid + 1, r, pos);
return q;
}
int query(int p, int q, int l, int r) {
if(l == r) return tr[q].cnt - tr[p].cnt;
int mid = l + r >> 1;
int lcnt = tr[tr[q].ls].cnt - tr[tr[p].ls].cnt;
int rcnt = tr[tr[q].rs].cnt - tr[tr[p].rs].cnt;
if(lcnt > rcnt) return query(tr[p].ls, tr[q].ls, l, mid);
//若左子区间出现次数大于一半,就走到左子树上
else return query(tr[p].rs, tr[q].rs, mid + 1, r);
//否则走到右子树上
}
int main() {
scanf("%d%d", &n, &m);
int a;
for(int i = 1; i <= n; i++) {
scanf("%d", &a);
root[i] = insert(root[i - 1], 1, n, a);
}
int l, r;
while(m--) {
scanf("%d%d", &l, &r);
int len = r - l + 1;
int tmp = query(root[l - 1], root[r], 1, n);
printf("%d\n", max(1, (tmp << 1) - len));
}
return 0;
}