Codeforces Round 1011 (Div. 2)

写在前面

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

妈的 E 没冲出来可惜。

话说这么多年没打了居然还能保持表现分 2k 上下、、、还是有点脑子的可能。

A

签到,特判。

  • \(s<t\) 一定有解;
  • \(s\ge t\)\(k=0\),一定无解;
  • \(s\ge t\)\(k\ge 1\),当且仅当字符串每个字符不全部相同时有解。
#include <bits/stdc++.h>
#define LL long long

int main() {
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  int T; std::cin >> T;
  while (T --) {
    int n, k; std::cin >> n >> k;
    std::string s; std::cin >> s;
    std::string t; t = s;
    std::reverse(t.begin(), t.end());

    if (s < t) {
      std::cout << "Yes" << "\n";
      continue;
    }
    if (k == 0) {
      std::cout << "No" << "\n";
      continue;
    }

    bool flag = 0;
    for (int i = 1; i < n; ++ i) if (s[i] != s[i - 1]) flag = 1;
    
    std::cout << (flag ? "Yes" : "No") << "\n";
    continue;
  }
  return 0;
}

B

枚举,构造。实现。

最终令整个数列变成只有一个 0,则最后一步操作一定是 \((1, |a|)(|a| \ge 2)\),且此时整个数列均为正数。

考虑如何消除数列中所有 0,且保证数列长度大于 1。对于一次操作 \([l, r]\),若其中有 \(a_i = 0(l\le i\le r)\),则操作后的结果一定是正数,则对原数列中所有 0 均进行一次操作即可令整个数列为正。

\(n\ge 4\),一种比较简单的实现是枚举原数列中两两相邻的数,若其中有 0 则操作一次,否则不操作;注意当 \(n\) 为奇数时,最后一段变为对三个数操作即可。通过这种做法即可保证消除所有 0。

#include <bits/stdc++.h>
#define LL long long
#define pr std::pair
#define mp std::make_pair

const int kN = 5010;
int n, a[kN];

int main() {
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> n;
    for (int i = 1; i <= n; ++ i) std::cin >> a[i];
    
    std::vector<pr <int, int> > ans;
    int cnt = 0, sz = 0;
    for (int i = 1; i <= n; i += 2) {
      if (i + 2 == n) {
        if (a[i] == 0 || a[i + 1] == 0 || a[i + 2] == 0) {
          ans.push_back(mp(i - cnt, i - cnt + 2));
          ++ cnt;
          ++ sz;
        } else {
          sz += 3;
        }
        break;
      }

      if (a[i] == 0 || a[i + 1] == 0) {
        ans.push_back(mp(i - cnt, i - cnt + 1));
        ++ cnt;
        ++ sz;
      } else {
        sz += 2;
      }
    }

    ans.push_back(mp(1, sz));
    std::cout << ans.size() << "\n";
    for (auto [x, y]: ans) std::cout << x << " " << y << "\n";
  }
  return 0;
}

C

构造,二进制。

众所周知异或可以看作不进位加法,则若 \((a + k) + (b + k) = (a + k) \oplus (b + k)\) 成立,说明等式左侧的整数加法中一定没有出现进位,否则一定有 \((a + k) + (b + k) > (a + k) \oplus (b + k)\),则某个 \(k\) 合法当且仅当 \((a + k)\)\((b + k)\) 二进制各位不同时为 1,从而不出现进位即可。

显然一种简单的构造是令 \(\max(a, b)\) 变为某个比较大的 \(2\) 的幂即可,则当且仅当 \(a=b\) 无解。

#include <bits/stdc++.h>
#define LL long long

int main() {
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  int T; std::cin >> T;
  while (T --) {
    LL a, b; std::cin >> a >> b;
    if (a == b) {
      std::cout << -1 << "\n";
      continue;
    } 
    if (a + b == (a ^ b)) {
      std::cout << 0 << "\n";
      continue;
    }

    if (a < b) std::swap(a, b);
    std::cout << (1ll << 30) - a << "\n";
  }
  return 0;
}

D

反悔贪心,正难则反。

吃完有奖励,吃不完有惩罚(悲

要保证最后所有 sushi 都要被吃完,手玩下很容易发现拿最后一盘 sushi 的时间一定不能晚于 \(n-k\),否则吃不完有惩罚。在此基础上要保证倒数第二盘也有足够时间吃完,则拿倒数第二盘的时间不能晚于 \(n - 2(k + 1) + 1\),倒数第三盘拿取时间不能晚于 \(n - 3(k + 1) + 1\)……进一步推广,拿取倒数第 \(i\) 盘的时间不能晚于 \(n - i(k + 1) + 1\)

问题转化为对于倒数第 \(i\) 盘 sushi,保证其被拿取的时间 \(\le n - i(k + 1) + 1\),求最多的价值之和。越靠前的 sushi 拿取的限制越严格,于是考虑枚举当前拿到了第几盘 sushi,然后倒序枚举 sushi 进行反悔贪心。考虑使用小根堆维护当前已经拿了的所有 sushi,若当前拿的盘数不足上限,则直接拿当前 sushi;否则比较当前 sushi 和堆顶的哪个更优,若更优则弹出堆顶的 sushi 反悔即可。

考虑优先队列实现,总时间复杂度 \(O(n\log n)\) 级别,实现详见代码。

#include <bits/stdc++.h>
#define LL long long

const int kN = 2e5 + 10;

int n, k, d[kN];

int main() {
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> n >> k;
    for (int i = 1; i <= n; ++ i) std::cin >> d[i];

    LL ans = 0;
    std::priority_queue<int> q;
    for (int i = 1; n - i * (k + 1) + 1 >= 1; ++ i) {
      for (int j = n - i * (k + 1) + 1; j > std::max(0, n - (i + 1) * (k + 1) + 1); -- j) {
        if ((int) q.size() < i) {
          ans += d[j];
          q.push(-d[j]);
        } else if (d[j] > -q.top()) {
          ans -= -q.top();
          q.pop();
          ans += d[j];
          q.push(-d[j]);
        }
      }
    }
    std::cout << ans << "\n";
  }
  return 0;
}
/*
1
7 1
3 1 4 1 5 9 2
*/

E

枚举,数学。

呃呃赛时做法方向是对的但是还是没想到关键转化枚举太多了死掉了。

发现 \(n\le 10^4\),允许做一些枚举。

首先判断 \(k > \max a_i\) 的情况,即取模对数列 \(a\) 无影响是否有解。然后假设实际的模数为 \(k\) 时有解,即存在可重集合 \(\{a_i \bmod k\} = \{b_i\}\),因为取模运算相当于对每个 \(a_i\) 减去了若干 \(k\),则 \(\sum a - \sum b\) 一定是 \(k\) 的倍数。则合法的 \(k\) 一定是 \(\sum a - \sum b\) 的约数,且一定满足 \(\max b < k \le \max a\)

发现 \(\sum a - \sum b \le 10^4\times 10^6 \approx 10^{10}\),约数数量并不大,于是考虑枚举其所有满足 \(\max b < k \le \max a\) 的约数 \(k\) 进行检查。检查时相当于判断可重集合 \(\{a_i \bmod k\}\)\(\{b_i\}\) 是否相同,考虑处理出两个集合中所有数的出现次数 \(\operatorname{cnta}\)\(\operatorname{cntb}\) 并比较是否相同即可。

注意到 \(\max b < k\),则 \(\operatorname{cntb}\) 在每次检查时都是固定的,可以直接预处理,则每次检查时仅需计算 \(\operatorname{cnta}\) 即可,可以小小卡常。

单次检查 \(O(n)\) 级别,则总时间复杂度 \(O\left(n\sqrt{nv}\right)\) 级别但是完全跑不满,实际运行效率很高。

#include <bits/stdc++.h>
#define LL long long

const int kN = 1e4 + 10;
const int kM = 1e6 + 10;
int n, a[kN], b[kN], cnt1[kM], cnt2[kM];
LL suma, sumb;

bool check(int p_) {
  if (p_ > a[n] || p_ <= b[n]) return 0;

  for (int i = 1; i <= n; ++ i) cnt1[a[i] % p_] ++;
  bool ret = 1;
  for (int i = 1; i <= n; ++ i) {      
    if (cnt1[a[i] % p_] != cnt2[a[i] % p_] || 
        cnt1[b[i]] != cnt2[b[i]]) {
      ret = 0;
      break;
    }
  }
  for (int i = 1; i <= n; ++ i) -- cnt1[a[i] % p_];
  return ret;
}

int main() {
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> n;
    suma = 0, sumb = 0;
    for (int i = 1; i <= n; ++ i) std::cin >> a[i], suma += a[i];
    for (int i = 1; i <= n; ++ i) std::cin >> b[i], sumb += b[i];
    std::sort(a + 1, a + n + 1);
    std::sort(b + 1, b + n + 1);

    bool flag = 1;
    for (int i = 1; i <= n; ++ i) if (a[i] != b[i]) flag = 0;
    if (flag) {
      std::cout << 1000000000ll << "\n";
      continue;
    }
    if (a[n] < b[n] || suma - sumb < 0) {
      std::cout << -1 << "\n";
      continue;
    }

    int ans = -1;
    for (int i = 1; i <= n; ++ i) ++ cnt2[b[i]];
    for (LL d = 1, val = suma - sumb; d * d <= val; ++ d) {
      if (val % d) continue;
      if (check(d)) {
        ans = d;
        break;
      }
      if (d * d != val && check(val / d)) {
        ans = val / d;
        break;
      }
    }
    for (int i = 1; i <= n; ++ i) cnt2[b[i]] = 0;
    std::cout << ans << "\n";
  }
  return 0;
}

F

猜猜我什么时候补

写在最后

学到了什么:

  • D:正难则反;
  • E:取模运算可以看作减去了若干个模数。

然后是夹带私货,哦哦牛批还能这么剪的同时喜欢稻叶昙和鱼韵的挺爽了:

posted @ 2025-03-23 02:53  Luckyblock  阅读(234)  评论(4)    收藏  举报