[ABC449] E
[ABC449] E
1600
给你一个整数N,M,还有一个长度为N的整数序列\(A=(A_{1},A_{2},\ldots,A_{N})\),序列中的每个元素都在1到M(含)之间。
接下来对这个整数序列A进行\(10^{100}\)次操作:
- 找出A中出现次数最少的整数,这个整数在1到M(含)之间。如果有多个出现次数一样少的数,选最小的那个。然后把这个数v追加到A的末尾。
你有Q个查询。第i个查询给出一个整数\(X_{i}\),请你找出执行完\(10^{100}\)次操作后,序列中第\(A_{X_{i}}\)个位置的数是多少。
题解:扫描线+离线+查询第k小+线段树二分
当操作非常多次以后,\(1,2,3,...,m\) 会重复出现,直接用取模运算就可以判断。
我们把询问排序,离线处理。
操作次数比较小的时候就是扫描线了,想象有一条水平线 pt,pt 一下的点都会被填满,然后问题变成了求第 k 小的问题。方法有很多。
线段树+二分
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e5+5,M=N;
int n,m,q,a[N],cnt[M],ans[N];
vector<int> val[N];
ll lim[N];
struct Query {
ll x; int id;
} t[N];
#define lson(x) (x<<1)
#define rson(x) (x<<1|1)
int tr[N*4];
// 把左右孩子的答案合并到父亲
void push_up(int rt){
tr[rt]=tr[lson(rt)]+tr[rson(rt)];
}
int query(int rt, int l, int r,int kth) {
if(l==r) return l;
int mid = (l + r) >> 1;
if(kth<=tr[lson(rt)]){
return query(lson(rt),l,mid,kth);
}
return query(rson(rt),mid+1,r,kth-tr[lson(rt)]);
}
void change(int rt,int l,int r,int x,int k){
if(l==r){
tr[rt]+=k;
return;
}
int mid=(l+r)>>1;
if(x<=mid) change(lson(rt),l,mid,x,k);
else change(rson(rt),mid+1,r,x,k);
push_up(rt);
}
int main() {
ios::sync_with_stdio(0);
cin>>n>>m;
for(int i=1;i<=n;++i) {
cin>>a[i];
cnt[a[i]]++;
}
for(int i=1;i<=m;++i)
val[cnt[i]].push_back(i);
int tmp=m;
for(int i=1;i<=n;++i) {
tmp-=val[i-1].size();
lim[i]=lim[i-1]+(m-tmp);
}
cin>>q;
for(int i=1;i<=q;++i) {
cin>>t[i].x;
t[i].id=i;
}
sort(t+1,t+q+1,[](const Query &i,const Query &j) {
return i.x<j.x;
});
int pt=0;
for(auto v:val[0])
change(1,1,m,v,1);
for(int i=1;i<=q;++i) {
ll x=t[i].x;
if(x<=n) {
ans[t[i].id]=a[x];
continue;
}
x-=n;
while(pt<n&&x>lim[pt+1]) {
++pt;
for(auto v:val[pt])
change(1,1,m,v,1);
}
x-=lim[pt];
if(pt==n) {
ans[t[i].id]=(x-1)%m+1;
} else {
ans[t[i].id]=query(1,1,m,x);
}
}
for(int i=1;i<=q;++i)
cout<<ans[i]<<endl;
return 0;
}

浙公网安备 33010602011771号