题意:
给定数组。q 次询问,每次问至少把 \(a[l,r]\) 拆成几个子序列,才能让每个子序列中的众数的出现次数 不大于子序列长度/2上取整
\(n,q\le 3e5, 1\le a_i\le n\)
思路:
绝对众数:出现次数严格大于N/2
如果区间众数不是绝对众数,则答案为1。否则,设绝对众数的出现次数为 x
,把其他所有数(共 n-x 个)和 n-x+1 个绝对众数分一组,剩下的绝对众数每个单独一组,组数是 x-(n-x+1)+1=2x-n。这样就是最优的!(真的)
众数可以用莫队求。但根据绝对众数的性质,还有些其他方法
法一:随机
绝对众数的出现次数大于一半,随便选一个数,它不是绝对众数的概率约 1/2,随机选40次左右就很稳,每次选出来都二分求出现次数
实测随机40次要2秒,30次1.7秒,25次居然wa
mt19937 rng(chrono::steady_clock::now().time_since_epoch().count()); ll ran(ll l, ll r) { return uniform_int_distribution<int>(l,r)(rng); } const signed N = 3 + 3e5; int n, q, a[N]; vector<int> pos[N]; int cnt(int l, int r, int x) { //x在区间中的出现次数 return upper_bound(all(pos[x]),r) - lower_bound(all(pos[x]),l); } signed main() { iofast; cin >> n >> q; for(int i = 1; i <= n; i++) cin >> a[i], pos[a[i]].pb(i); while(q--) { int l, r; cin >> l >> r; int ans = 0; for(int i = 1; i <= 30; i++) ans = max(ans, cnt(l,r,a[ran(l,r)])); cout << max(1, 2*ans-(r-l+1)) << endl; } }
法二:线段树
绝对众数肯定也是至少一个儿子的绝对众数
节点维护区间的绝对众数
查询可谓非常暴力啊。所有走过的区间的绝对众数都作为候选,每个都求(在整个大区间中的)出现次数,取max返回
复杂度俩log,不到一秒
const signed N = 3 + 3e5; int n, q, a[N]; vector<int> pos[N]; int cnt(int l, int r, int x) { //x在区间中的出现次数 return upper_bound(all(pos[x]),r) - lower_bound(all(pos[x]),l); } #define mid ((l+r)/2) #define lson u*2 #define rson u*2+1 #define ls lson,l,mid #define rs rson,mid+1,r int tr[N<<2]; //区间众数 void build(int u, int l, int r) { if(l == r) tr[u] = a[l]; else build(ls), build(rs), tr[u] = cnt(l,r,tr[lson]) > cnt(l,r,tr[rson]) ? tr[lson] : tr[rson]; } int ask(int u, int l, int r, int x, int y) { //返回区间绝对众数的出现次数 if(y < l || x > r || y<x) return 0; //没交集 if(x <= l && r <= y) return cnt(x,y,tr[u]); //包含 return max(ask(ls, x, y), ask(rs, x, y)); } signed main() { iofast; cin >> n >> q; for(int i = 1; i <= n; i++) cin >> a[i], pos[a[i]].pb(i); build(1, 1, n); while(q--) { int l, r; cin >> l >> r; cout << max(1, 2*ask(1,1,n,l,r)-(r-l+1)) << endl; } }