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;
}
posted @ 2024-07-23 14:36  Brilliant11001  阅读(2)  评论(0编辑  收藏  举报