CF1806F2 GCD Master (hard version)

先刻画最优解的形式。

不妨把最终序列看成若干个可重集合,每个集合内是原序列的数,贡献是集合的 \(\gcd\)

考虑两个集合 \(S\)\(T\),设其 \(\gcd\) 分别为 \(x\)\(y\),且 \(x\le y\)

考虑一个调整方法:取出 \(T\) 中最大值 \(v\),然后把 \(S\)\(T\setminus\{v\}\) 合并。这样贡献和会 \(-x-y+v+\gcd(x,\gcd_{t\in T\setminus\{v\}}t)\)。注意到如果 \(v\ge 2y\),那么这个贡献和会 \(>0\),与当前为最优解矛盾。所以此时 \(T\) 中的元素一定全为 \(y\)

那么除了 \(\gcd\) 最小的集合 \(S\) 外,其余的所有集合内都只有一种元素。同时我们可以发现,如果存在 \(x\in S\),但是 \(x\) 还存在于另一个集合 \(T\) 中,此时 \(T\) 中一定也只有 \(x\) 一种元素,那么我们把 \(x\)\(S\) 移动到 \(T\) 一定是不劣的。

于是我们可以说明:对于任意 \(x\in S\)\(=x\) 的所有数都在 \(S\) 中。于是可以考虑先进行相同数的合并,再对不同的数进行合并。在对于不同数进行合并时,存在的重复的数就不重要了,因此可以认为 \(S\) 面临的情况是固定的,也就是把序列 \(a\) 去重后的结果。

观察到合并相同数和合并不同数两个的过程其实是互不影响的,因此可以分开枚举两者的合并次数,不妨设为 \(c\)\(k-c\) 次。

合并相同数是容易的,显然我们操作的一定是值域上的前缀,那么排序后扫一遍即可。

而合并不同数看起来没有那么好做。不妨设序列 \(b\)\(a\) 去重后的结果。那么我们需要处理的是如下问题:对于每个 \(c\),从 \(b\) 中选出 \(c\) 个数 \(v_1,v_2,\cdots,v_c\),最小化 \(\sum v-\gcd v\)

对于这个问题,同样可以考虑调整。从直觉上来说,\(\sum v\) 是远大于 \(\gcd\) 的,我们可能需要在保证和不大的情况下调整,来尽量让 \(\gcd\) 比较大。

考虑先取出 \(b\) 的前 \(c\) 个元素作为 \(v\)。我们首先有结论:我们只会调整 \(\le 1\) 个数。

证明:如果我们调整了 \(\ge 2\) 个数,那么它们调整后在 \(b\) 中的排名一定 \(>c\)。随便取两个调整后的数,不妨设它们在 \(b\) 中排名为 \(i,j(i<j)\),那么和的变化量是 \(\ge b_j-b_i\) 的,而新的 \(\gcd\le \gcd(b_i,b_j)\le b_j-b_i\),因此这么做是不优的。

进一步地,如果我们调整了一个数 \(v_i\) 变成 \(b_j\),此时仍然有 \(j>c\),那么比较它和 \(v_c\) 也可以说明不优,于是我们只可能调整 \(v_c\)

这意味着,存在一组最优解,它包含了 \(b_{1\sim c-1}\),只有最后一个数不固定。设 \(g=\gcd_{1\le i\le c-1}b_i\),那么最后一个数 \(b_t\) 一定最小化了 \(b_t-\gcd(b_t,g)\),这个如果确定了 \(g\),那么一遍枚举即可。

现在我们要对每个 \(c\) 都求出这个答案。这个是不难的,观察到 \(g\) 为前缀 \(\gcd\),因此变化次数 \(O(\log V)\),每次变化时暴力更新 \(\gcd(b_t,g)\) 并维护后缀最值可以做到 \(O(n\log^2 V)\)

后面只要加一个小优化,我们不需要每次都求 \(\gcd(b_t,g)\),而是直接在上一次求 \(\gcd\) 的结果上求解,这样可以被均摊分析到单 \(\log\)

最后只要再将相同数和不同数的情况合并即可,总复杂度 \(O(n\log V)\)

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using i128 = __int128;

ostream& operator << (ostream &o, i128 v) {
  if(v >= 10) o << v / 10;
  o << char(v % 10 + 48);
  return o;
}

const int kN = 1e6 + 5;
int n, k;
ll m, a[kN], b[kN], gb[kN], suf[kN];
i128 cost[kN], val[kN];

void Solve() {
  cin >> n >> m >> k;
  memset(cost, 0, (n + 1) * sizeof(i128));
  i128 all = 0;
  for(int i = 1; i <= n; i++) {
    cin >> a[i];
    all += a[i];
  }
  sort(a + 1, a + n + 1);
  memcpy(b, a, (n + 1) * sizeof(ll));
  int tot = unique(b + 1, b + n + 1) - b - 1;
  memcpy(gb, b, (tot + 1) * sizeof(ll));
  int p = 1;
  for(int l = 1, r; l <= n; l = r + 1) {
    for(r = l; (r < n) && (a[r + 1] == a[l]); r++) ;
    int len = r - l + 1;
    for(int i = 1; i < len; i++, p++) {
      cost[p] = cost[p - 1] + a[l];
    }
  }
  ll gcd = 0;
  i128 pre = 0;
  for(int i = 2; i <= tot; i++) {
    ll old = gcd;
    pre += b[i - 1];
    gcd = __gcd(gcd, b[i - 1]);
    if(gcd != old) {
      for(int j = i; j <= tot; j++) {
        gb[j] = __gcd(gb[j], gcd);
      }
      suf[tot] = b[tot] - gb[tot];
      for(int j = tot - 1; j >= i; j--) {
        suf[j] = min(suf[j + 1], b[j] - gb[j]);
      }
    }
    val[i] = pre + suf[i];
  }
  i128 ans = 0;
  for(int c = 0; c <= min(p - 1, k); c++) {
    if(k - c + 1 <= tot) {
      ans = max(ans, all - cost[c] - val[k - c + 1]);
    }
  }
  cout << ans << "\n";
}

int main() {
  ios::sync_with_stdio(0), cin.tie(0);
  int t;
  cin >> t;
  while(t--) Solve();
  return 0;
}
posted @ 2025-07-12 17:13  CJzdc  阅读(26)  评论(0)    收藏  举报