cf1514 D. Cut and Stick
作者:互联网
题意:
给定数组。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;
}
}
标签:Cut,cf1514,int,绝对,pos,次数,Stick,众数,define 来源: https://www.cnblogs.com/wushansinger/p/16201283.html