Codeforces Round 973 (Div. 2)

写在前面

比赛地址:https://codeforces.com/contest/2013

最后 3min 绝杀 E 上分场爽,然而前期太唐了呃呃要是不唐准能红名表现分。

妈的什么时候上黄!

A 签到

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
//=============================================================
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    int n, x, y, ans; std::cin >> n >> x >> y;
    if (x < y) {
      ans = ceil(1.0 * n / x);
    } else {
      ans = ceil(1.0 * n / y);
    }
    std::cout << ans << "\n";
  }
  return 0;
}

B 结论,贪心

发现第 \(n\) 个人会且一定会和第 \(n-1\) 个人打,则最终的 \(a_n\) 中一定有贡献 \(-a_{n-1}\)

然后考虑到 \(a_i \ge 1\),则答案的上界即令所有 \(a_1\sim a_{n-2}\)\(a_n\) 的贡献均为正值 \(a_i\)

发现仅需令 \(a_1\sim a_{n-2}\) 先分别和 \(a_{n-1}\) 打,再令 \(a_{n-1}\)\(a_{n}\) 打即可达到上界。则答案即为:

\[a_n - a_{n-1} + \sum_{1\le i\le n - 2} a_i \]

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
//=============================================================
int n;
LL a[kN];
//=============================================================
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> n;
    LL sum = 0;
    for (int i = 1; i <= n; ++ i) std::cin >> a[i];
    for (int i = 1; i < n - 1; ++ i) sum += a[i];
    std::cout << a[n] - a[n - 1] + sum << "\n";
  }
  return 0;
}

C 交互

考虑每次往后面接一个字符并询问,若为真则固定该位,否则更换字符再询问,直至往后接 0/1 询问都为假。

发现此时的字符串一定为原串的一个后缀,然后考虑再往前不断接一个字符并询问,若为真则固定该位,否则可以保证该位一定为另一个字符。

易证操作数量不超过 \(2n\) 次。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
int n;
std::map <std::string, bool> checked;
//=============================================================
bool query(std::string &t_) {
  if (checked.count(t_)) return checked[t_];

  std::cout << '?' << " " << t_ << "\n";
  std::cout.flush();
  bool ret; std::cin >> ret;
  return ret;
}
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  // std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> n;
    std::string s;
    checked.clear();

    while ((int)s.length() < n) {
      s.push_back('0');
      bool ret = query(s);
      checked[s] = ret;
      if (ret) continue;
      
      s.pop_back(), s.push_back('1');
      ret = query(s);
      checked[s] = ret;
      if (ret) continue;

      s.pop_back();
      break;
    }
    while ((int) s.length() < n) {
      std::string t = "0" + s;
      bool ret = query(t);
      checked[t] = ret;
      if (ret) {
        s = t; continue;
      }
      s = "1" + s;
    }
    std::cout << '!' << " " << s << "\n";
    std::cout.flush();
  }
  return 0;
}

D 贪心 or 二分答案

由给定的操作可知,显然存在一种最优方案,使得给定数列单调不降。

在这种限制下考虑使所有位置尽可能平均以最小化极差。

考虑对仅前 \(i\) 个位置操作后,其中的最小值的下界(不一定出现在位置 \(i\)),显然为:

\[\left\lfloor\frac{\sum\limits_{1\le j\le i} a_j}{i}\right\rfloor \]

考虑仅对后 \(n-i+1\) 个位置操作后,其中的最大值的上界(同样不一定出现在位置 \(i\)),显然为:

\[\left\lceil\frac{\sum\limits_{i\le j\le n} a_j}{n-i+1}\right\rceil \]

然后考虑到对整个数列进行操作,发现对整个数列操作后,可以达到的上下界即对上述的下界取最小值,对上界取最大值,则答案即为:

\[\max_{1\le i\le n} \left\lceil\frac{\sum\limits_{i\le j\le n} a_j}{n-i+1}\right\rceil - \min_{1\le i\le n}\left\lfloor\frac{\sum\limits_{1\le j\le i} a_j}{i}\right\rfloor \]

总时间复杂度 \(O(n)\) 级别。

还有大力二分答案的做法,详见其他题解。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
//=============================================================
int n;
LL a[kN];
//=============================================================
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> n;
    for (int i = 1; i <= n; ++ i) std::cin >> a[i];
    
    LL s = 0, mina = a[1], maxa = 0;
    for (int i = 1; i <= n; ++ i) {
      s += a[i], mina = std::min(mina, s / i);
    }
    s = 0;
    for (int i = n; i; -- i) {
      s += a[i], maxa = std::max(maxa, (LL) ceil(1.0 * s / (n - i + 1)));
    }
    std::cout << maxa - mina << "\n";
  }
  return 0;
}

E 贪心,数学,gcd

发现两个不相等的数 \(x, y\)\(\gcd\),一定有 \(\gcd(x, y)\le \frac{\max(x, y)}{2}\),即每次向序列后面加一个数,前缀的 \(\gcd\) 都至少会除 2,则发现仅需通过不超过 \(\log_2 v\) 个数,即可令前缀 \(\gcd\) 快速地减小到全局 \(\gcd\)

则显然第一个位置一定放全局最小值,否则根据 \(\gcd\) 的算数基本定理的形式,全局最小值一定对质因数的幂次限制最多,则总可以通过调整使得全局最小值提前,使得总代价更小。

然后考虑每次 \(O(n)\) 地枚举向序列后面加的数并取使 \(\gcd\) 减小的最多的加上去即可,若加上后变为全局 \(\gcd\) 则可直接停止枚举。

总时间复杂度 \(O(n\log n)\) 级别。

赛时根据 \(\sum \max a_i \le 10^5\) 的性质找了一堆优化常数的小结论实际上并无必要呃呃,上面的关键结论出来直接大力做就过了。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e5 + 10;
//=============================================================
int n, a[kN];
LL alld, ans;
//=============================================================
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> n;
    ans = alld = 0;
    for (int i = 1; i <= n; ++ i) {
      std::cin >> a[i];
      alld = std::__gcd(alld, 1ll * a[i]);
    }
    std::sort(a + 1, a + n + 1);

    int nowd = a[1];
    ans += nowd;
    for (int i = 2; i <= n; ++ i) {
      if (nowd == alld) {
        ans += 1ll * alld * (n - i + 1);
        break;
      }
      int newd = nowd;
      for (int j = 1; j <= n; ++ j) {
        newd = std::min(newd, std::__gcd(nowd, a[j]));
      }
      nowd = newd, ans += nowd;
    }
    std::cout << ans << "\n";
  }
  return 0;
}
/*
1
3
10 15 6

1
5
6 42 12 52 20
*/

F1

什么东西我去

写在最后

学到了什么:

  • D:感觉是平均所有位置最小化极差的套路。
  • E:gcd 的单调性,最近碰到好几次了呃呃
posted @ 2024-09-23 14:25  Luckyblock  阅读(304)  评论(0编辑  收藏  举报