【校内训练2019-11-08】能量运输

【题目概括】

\(n\)个点的环首尾相接,每个点都有一个权值\(val_i\)。(保证\(n|\sum val_i\)

我们可以进行的操作是将当前\(i\)个点的权值转移一部分到与其相隔\(k\)个点的点上,并且产生转移量的贡献。

题目的最终是让所有的点的权值相同,请你最小化转移的贡献。

【思路要点】

  • 这个就是环形均分纸牌,将每次只能移动相邻的点的权值改成了可以移动多个位置的。
  • 那么我们采取的决策就是每一个\(k\)的环拉出来,单独做一遍环形均分纸牌。
  • 这样讲所有的环都处理完,所有答案的和就是题目的最小贡献。
  • 时间复杂度 \(\mathcal O(nlogn)\)

【代码】

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int N = 5e5 + 5;

int n, k, cnt;
ll w[N], g[N], tmp[N];
bool vis[N];
ll ave, ans = 0;

void move(int& p) {
  p += k + 1;
  if (p > n)
    p -= n;
}

ll getAns(int p) {
  cnt = 0;
  while (!vis[p]) {
    vis[p] = 1;
    g[++cnt] = w[p];
    move(p);
  }
  tmp[1] = 0;
  for (int i = 2; i <= cnt; i++)
    tmp[i] = tmp[i - 1] + g[i - 1] - ave;
  sort(tmp + 1, tmp + 1 + cnt);
  ll res = 0, zw = tmp[(cnt + 1) >> 1];
  for (int i = 1; i <= cnt; i++)
    res += abs(tmp[i] - zw);
  return res;
}

int main() {
  ave = 0;
  scanf("%d %d", &n, &k);
  for (int i = 1; i <= n; i++) 
    scanf("%lld", &w[i]), ave += w[i];
  ave /= n;
  for (int i = 1; i <= n; i++) 
    if (!vis[i]) 
      ans += getAns(i);
  printf("%lld\n", ans);
  return 0; 
}

猜你喜欢

转载自www.cnblogs.com/chhokmah/p/11829603.html