C. Insert and Equalize题目讲解

题目:Problem - C - Codeforces

首先先审题,最关键的是insert和add这个字眼,insert这个先放一边,先考虑不插入a(n+1)的情况,add就已经限制了我们只能往高的加,打个比方拿第二个样例举例,我们不可能把17变成1,因为add已经限制我们只能加二不能减,所以在没有插入a(n+1)的情况下,我们需要把所有数都加上一定数量的x,变成17。这是第一个点:我们最后只能让所有数都和最大数的相同。

如果我们要insert一个数,假如我们输入的数据是
 

3

1 2 3

 在第一个点里也说了,我们最后要把所有的数变成最大的,这里最大的也就是3。所以最好的情况是需要插入一个和3最靠近的数,在这个样例中我们插入0或者4,增加的操作次数是一样的,这里的x是1,x怎么求的我们待会讲,0变成3需要3次,1需要2次,2需要1次,总共6次,如果插入4同样是6次。但我们看样例2,这里的x是4。

C. Insert and Equalize题目讲解_第1张图片

画图来看插入13显然能使操作次数最小,它最靠近17,不用向0或者18那样多上一层。这样我们大胆推测选择最靠近最大值的那个数就是我们要的a(n+1)这里的靠近指的是差别为一个操作次数的数,且没有出现过的数。

我们不妨再来推测一个结论,假设我们已经找到x,而且我们已经知道最大值 ,且n=4

C. Insert and Equalize题目讲解_第2张图片

如果它是连续的,我们能够找到的最小值为m-3x,连续意味着我们只能使用m+x或者m-4x,这两个值的插入都会让操作次数增加n次 ,所以我们没法找到像m-2x这样只用增加2x次的数。这个结论很重要,在后面的代码中我们会找一个mex,这个mex指的是a(n+1)需要的操作次数,怎么找的呢,我们会用到一个for循环,记录数组中两个元素之前的差值出现的次数,如果像图中这样3,2,1都出现过了,我们就说是连续的。这里不妨先看看代码。

int max = *std::max_element(a.begin(), a.end());

我们会先找到最大值。

std::vector cnt(n + 1);
i64 ans = 0;
int mex = 0;
for (int i = 0; i < n; i++) {
    //求出到最大值所需要的操作次数,这里的g就是我们所说的x
    a[i] = (max - a[i]) / g;
    if (a[i] <= n) {
        cnt[a[i]] += 1;
    }
    ans += a[i];
}

用一个cnt数组记录连续差值为x的个数,ans首先加上数组中其它元素到max所需要的操作次数,这是插不插入都要消耗的次数。

while (cnt[mex]) {
    mex++;
}
ans += mex;

看到上一个代码中定义的0没,从靠近max的地方开始找,每次++就相当于找减去一个操作次数的数是否存在,拿样例1举例,(3-3)/1=0,(3-2)/1=1;(3-1)/1=2;我们发现,mex的大小刚好等于3=n再插入4以后我们也要加上一个n=3;再拿样例二举例,(17-17)/4=0;但13并不存在所以              (17-13)/4=1,1个数数不存在,及cnt[1]=0;翻回我之前画的那个图。

C. Insert and Equalize题目讲解_第3张图片

是不是正好13需要一次操作就等于17了。 ans+=mex及ans+1=27;答案就是这么计算出来的,现在还剩最后一个问题,怎么确定x?

首先你要思考,我们x越大才能减少操作次数对吧,那么我们就找一个符合条件的最大的x,这个条件是什么呢,肯定是找到所有相邻两个数差值的最大公因数(注意不能找所有最大值与每个元素差值的最大公因数,这个点暂时没搞清楚为什么,但是会出错,有大佬知道可以在评论区讲解一下)。

接下来就是代码,注意,只有一个元素的时候要特判一下

    
    int n;
    std::cin >> n;

    std::vector a(n);
    for (int i = 0; i < n; i++) {
        std::cin >> a[i];
    }
    if (n == 1) {
        std::cout << 1 << "\n";
        return;
    }

    int g = 0;
    for (int i = 1; i < n; i++) {
        g = gcd(g, a[i] - a[i - 1]);
    }

为什么是1看原题目下面的note第三条。

using i64 = long long;

i64 gcd(i64 a, i64 b) {
    // 辗转相除法计算最大公约数
    while (b != 0) {
        i64 temp = b;
        b = a % b;
        a = temp;
    }
    return std::abs(a);  // 返回最大公约数的绝对值
}

void solve() {
    int n;
    std::cin >> n;

    std::vector a(n);
    for (int i = 0; i < n; i++) {
        std::cin >> a[i];
    }
    if (n == 1) {
        std::cout << 1 << "\n";
        return;
    }

    int g = 0;
    for (int i = 1; i < n; i++) {
        g = gcd(g, a[i] - a[i - 1]);
    }

    int max = *std::max_element(a.begin(), a.end());
    std::vector cnt(n + 1);
    i64 ans = 0;
    int mex = 0;
    for (int i = 0; i < n; i++) {
        a[i] = (max - a[i]) / g;
        if (a[i] <= n) {
            cnt[a[i]] += 1;
        }
        ans += a[i];
    }
    while (cnt[mex]) {
        mex++;
    }
    ans += mex;
    std::cout << ans << "\n";
}

这是总代码。

你可能感兴趣的:(算法)