CodeForces ~ 999D ~ Equalize the Remainders (思维 + set二分)

题意

n个数字,对m取余有m种情况,使得每种情况的个数都为n/m个(保证n%m=0),最少需要操作多少次?
操作:把某个数字+1。输出最少操作次数,和操作后的序列(可以输出任意一种)。


思路

对于余数x来说,如果余数为x的数没凑够n/m个,那么就让他不变,如果已经凑够了,我们就让他变为距离他最近的没凑够的余数(且通过+1得到的)。
所以,C[i]数组维护余数为 i i 的数的个数,定义sets,维护还没凑够的余数。所以对于每一个数字求出他的余数d,然后找到 s s 中第一个大于等于d的数字x,++C[x],如果C[x]==n/m就把x从s中删除。但是注意一种情况,x比s中最大的数还大,那么我们就把x变为s中最小的数,举个例子m=5,余数为4和为0的数字凑够了,此时又来一个余数为4的数,该数应该变为余数为1。维护答案和序列,ans += (x-d+m)%m,a[i] += (x-d+m)%m。

#include 
using namespace std;
const int MAXN = 2e5+5;
typedef long long ll;
ll n, m, a[MAXN], c[MAXN], ans;
set<int> s;
int main()
{
    scanf("%lld%lld", &n, &m);
    for (int i = 0; i < m; i++) s.insert(i);
    for (int i = 0; i < n; i++)
    {
        scanf("%lld", &a[i]);
        int d = a[i]%m, x;
        //找到d能加到的最近的余数x
        if (d > *s.rbegin()) x = *s.begin();//d比最大的还大
        else x = *s.lower_bound(d);
        if (++c[x] == n/m) s.erase(x);//余数为x的数凑够了
        ans += (x-d+m)%m;
        a[i] += (x-d+m)%m;
    }
    printf("%lld\n", ans);
    for (int i = 0; i < n; i++) printf("%lld ", a[i]);
}
/*
6 3
3 2 0 6 10 12
*/

你可能感兴趣的:(【思维/构造】,【二分/尺取/差分】)