贪心、二分、枚举倍数

题没读懂,诅咒出题人了。

给定长度为 $n$ 的序列 $a$,询问 $q$ 次,每次给定一个 $x$,可以进行任意次如下操作。

  • 将一个不小于 $x$ 的 $a_i$ 减去 $x$。

最小化操作后 $a$ 的中位数,注意当 $n$ 为偶数时中位数为中心两个元素中较大的那个。

容易发现答案为模 $x$ 意义下序列的中位数,难点在于如何计算。注意到值域限制 $a_i \leq n$,而询问次数 $q$ 与 $n$ 同阶。对同一个序列多次询问相同的 $x$ 没有意义,所以最坏情况下应该是 $x$ 从 $1$ 到 $n$ 被问了一遍。

对于每个 $x$ 二分答案后枚举 $x$ 的所有倍数,因为 $x$ 将值域切割成了若干区间,每个区间都只有一段前缀对答案有贡献,使用前缀和快速求出每个区间的前缀贡献。预处理 $x \in [1,n] \cap \mathbb{Z}$ 的所有答案即可。

整体时间复杂度 $O(n \log ^2 n)$。

#include<bits/stdc++.h>
using namespace std;
int n,q,med,ans[100'005],sum[100'005];
int calculate(int x){
    int l=0,r=x-1,ret=x;
    while(l<=r){
        int mid=(l+r)>>1,cnt=sum[mid];
        for(int i=x;i<=n;i+=x) cnt+=sum[min(i+mid,n)]-sum[i-1];
        if(cnt<med) l=mid+1;
        else ret=mid,r=mid-1;
    }
    return ret;
}
void solution(){
    cin>>n>>q,med=(n+2-(n&1))/2;
    for(int i=1;i<=n;i++){
        static int x;
        cin>>x,++sum[x];
    }
    for(int i=1;i<=n;i++) sum[i]+=sum[i-1];
    for(int i=1;i<=n;i++) ans[i]=calculate(i);
    for(int i=1;i<=q;i++){
        static int x;
        cin>>x;
        cout<<ans[x]<<" \n"[i==q];
    }
    for(int i=1;i<=n;i++) sum[i]=0;
}
int T;
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    cin>>T;
    while(T--) solution();
    return 0;
}
最后修改:2024 年 09 月 27 日
如果觉得我的文章对你有用,请随意赞赏