Codeforces Round 964 (Div. 4) (A - G2)

本家:Codeforces Round 964 (Div. 4)

A. A+B Again?

给定一个两位数,求两位数的数字和。

模拟。时间复杂度 O(1)。

#include <bits/stdc++.h>

using namespace std;
using ll = long long;

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);

  int tc;
  cin >> tc;
  while (tc--) {
    int x;
    cin >> x;

    cout << x / 10 + x % 10 << '\n';
  }

  return 0;
}

B. Card Game

给两个人每人两张卡片,卡片上有个数字。初始时四张卡片都是面朝下的,一局游戏进行两个回合,每回合两个玩家各自翻开一张卡片,卡片上数字严格大于对方的,该回合获胜。每局游戏中获胜回合数最多的人,该局游戏获胜。现在求所有可能进行的对局中,玩家一能获胜的对局数量。

有点绕的题,代码直接暴力写了所有可能的情况,比较丑陋。

模拟。时间复杂度 O(1)。

#include <bits/stdc++.h>

using namespace std;
using ll = long long;

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);

  int tc;
  cin >> tc;
  while (tc--) {
    int a, b, c, d;
    cin >> a >> b >> c >> d;

    int ans = (a > c and b >= d) or (a >= c and b > d);
    ans += (a >= d and b > c) or (a > d and b >= c);
    ans += (b >= c and a > d) or (b > c and a >= d);
    ans += (b > d and a >= c) or (b >= d and a > c);
    cout << ans << '\n';
  }

  return 0;
}

C. Showering

给定一个数轴 [0,m],和 n 段线段,线段霸占了数轴上的一段区间且线段之间互不重叠,求数轴上是否存在一段间隔至少为 s 的区间。

模拟。时间复杂度 O(n) 。

#include <bits/stdc++.h>

using namespace std;
using ll = long long;

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);

  int tc;
  cin >> tc;
  while (tc--) {
    int n, s, m;
    cin >> n >> s >> m;

    vector seg(n, pair(0, 0));
    for (auto& [l, r] : seg) {
      cin >> l >> r;
    }

    bool ok = seg.front().first >= s or m - seg.back().second >= s;
    for (int i = 1; i < n; i++) {
      ok |= seg[i].first - seg[i - 1].second >= s;
    }

    cout << (ok ? "YES\n" : "NO\n");
  }

  return 0;
}

D. Slavic's Exam

给定两个小写字符串 s 和 t , s 中某些位置的字符是 ? ,可以将其替换为任意小写字符。现在需要修改 s ,以满足 t 为 s 的子序列,输出构造方案或者判断不存在方案。

对 s 从前往后贪心匹配 t 的每一位即可, ? 字符直接修改为 t 当前正在和 s 匹配的字符。不太好文字描述,建议自己手搓几组字符串想一下。

贪心。时间复杂度 O(n) 。

#include <bits/stdc++.h>

using namespace std;
using ll = long long;

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);

  int tc;
  cin >> tc;
  while (tc--) {
    string s, t;
    cin >> s >> t;

    bool ok = false;
    for (int i = 0, j = 0; i < s.size() and j < t.size(); i++) {
      if (s[i] == t[j] or s[i] == '?') {
        s[i] = t[j];
        j++;
      }
      ok = j == t.size();
    }

    for_each(s.begin(), s.end(), [](auto& ch) { ch = ch == '?' ? 'a' : ch; });
    if (ok) {
      cout << "YES\n" << s << '\n';
    } else {
      cout << "NO\n";
    }
  }

  return 0;
}

E. Triple Operations

给定一个区间 [l,r] ,和一种操作:每次在区间内随便选择两个不同的数 x 和 y ,将其中一个乘三,另外一个除三向下取整。给定 q 次询问,求将给定区间所有数变为 0 的最小操作次数。

容易发现,每个数字能除以三的次数是有限的。由于区间的值域在 2e5 以内,所以可以预处理每个数的除三次数,然后用前缀和优化一下,这样除三的次数就搞定了。那么乘三的次数呢?发现对于每个数字,除了几次三,就要找另外一个数字乘几次三。如果这个数字非零,那么最终我们还要对这个数字再次进行多余的操作,所以找 0 进行乘三操作即可,那么如何最快的构造出一个 0 呢?当然是直接对当前的区间的左端点除三了,其产生的乘三次数也是当前区间内所有数字中最小的。

数论、前缀和。时间复杂度 O(Dom+q∗log(l)) 。单次询问可以优化成 O(1) 。

#include <bits/stdc++.h>

using namespace std;
using ll = long long;

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);

  constexpr int maxn = 2e5;
  vector pre(maxn + 1, 0ll);
  pre[1] = 1, pre[2] = 2;
  for (int i = 3, cur = 1; i <= maxn; i++) {
    if (pow(3, cur) == i) {
      cur++;
    }
    pre[i] = cur + pre[i - 1];
  }

  int tc;
  cin >> tc;
  while (tc--) {
    int l, r;
    cin >> l >> r;

    int cnt = 0;
    int t = l;
    while (t > 0) {
      t /= 3;
      cnt++;
    }
    cout << pre[r] - pre[l - 1] + cnt << '\n';
    // cout << pre[r] + pre[l] - pre[l - 1] * 2 << '\n'; O(1)写法
  }

  return 0;
}

F. Expected Median

给定一个长度为 n 的 01 数组 a ,对其中所有长度为 k (奇数)的子序列的中位数求和,输出求和后对 1e9+7 取模的结果。

中位数只有为 1 时才会对答案产生贡献,那么就枚举 1 的数量严格大于 0 的数量的所有方案,然后利用组合数公式对每种方案的数量进行计数即可。

组合数学。时间复杂度 O(n) 。

#include <bits/stdc++.h>

using namespace std;
using ll = long long;

template <int mod>
struct mint {
  int x = 0;
  mint() {}
  mint(ll x) : x(norm(x % mod)) {}

  int norm(int x) {
    x += x < 0 ? mod : 0;
    x -= x >= mod ? mod : 0;
    return x;
  }
  mint inv() { return (*this) ^ mod - 2; }
  mint& operator+=(mint rhs) { return x = norm(x + rhs.x), *this; }
  mint& operator-=(mint rhs) { return x = norm(x - rhs.x), *this; }
  mint& operator*=(mint rhs) { return x = 1ll * x * rhs.x % mod, *this; }
  mint& operator/=(mint rhs) { return *this *= rhs.inv(); }
  mint& operator^=(ll exp) {
    mint res(1);
    for (mint& base = *this; exp > 0; base *= base, exp /= 2) {
      if (exp & 1)
        res *= base;
    }
    return *this = res;
  }
  friend mint operator+(mint lhs, mint rhs) { return lhs += rhs; }
  friend mint operator-(mint lhs, mint rhs) { return lhs -= rhs; }
  friend mint operator*(mint lhs, mint rhs) { return lhs *= rhs; }
  friend mint operator/(mint lhs, mint rhs) { return lhs /= rhs; }
  friend mint operator^(mint lhs, ll exp) { return lhs ^= exp; }
  friend ostream& operator<<(ostream& os, mint rhs) { return os << rhs.x; }
  friend istream& operator>>(istream& is, mint& rhs) {
    ll x;
    cin >> x;
    rhs = x;
    return is;
  }
};

template <int mod>
struct Comb {
  using Z = mint<mod>;
  int n;
  vector<Z> fac, inv;
  Comb(int n) : n(n) {
    fac.resize(n + 1, 1);
    inv.resize(n + 1);
    for (int i = 1; i <= n; i++) {
      fac[i] = fac[i - 1] * i;
    }

    inv[n] = 1 / fac[n];
    for (int i = n; i; i--) {
      inv[i - 1] = inv[i] * i;
    }
  }

  Z A(int n, int m) { return fac[n] * inv[n - m]; }

  Z C(int n, int m) { return A(n, m) * inv[m]; }

  Z D(int n) {
    vector d(n + 1, Z(1));
    for (int i = 1, sign = -1; i <= n; i++, sign = -sign) {
      d[i] = i * d[i - 1] + sign;
    }
    return d[n];
  }

  Z C(int n) { return C(n * 2, n) - C(n * 2, n + 1); }

  Z s(int n, int m) {
    vector res(m + 1, Z(1));
    for (int i = 1; i <= n; i++) {
      for (int j = min(i, m); j > 0; j--) {
        res[j] = res[j - 1] + (i - 1) * res[j];
      }
      res[0] = 0;
    }
    return res[m];
  }

  Z S(int n, int m) {
    Z res = 0;
    for (int i = 0, sign = 1; i <= m; i++, sign = -sign) {
      res += sign * C(m, i) * (Z(m - i) ^ n);
    }
    return res * inv[m];
  }
};

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);

  constexpr int mod = 1e9 + 7;
  using Z = mint<mod>;

  int tc;
  cin >> tc;
  while (tc--) {
    int n, k;
    cin >> n >> k;

    int zero = 0, one = 0;
    for (int i = 0, x; i < n; i++) {
      cin >> x;
      zero += x == 0;
      one += x == 1;
    }

    Z ans;
    Comb<mod> comb(n);
    int mid = k / 2;
    for (int i = 0; i <= mid; i++) {
      if (zero < mid - i or one < mid + i + 1) {
        continue;
      }
      ans += comb.C(zero, mid - i) * comb.C(one, mid + i + 1);
    }

    cout << ans << '\n';
  }

  return 0;
}

G1. Ruler (easy version)

交互题。

有一把尺子在 [2,999] 的范围内遗失了一个刻度 x ,对于测量的长度 len ,如果小于 x ,测量结果是 len ,如果大于等于 x ,测量结果是 len+1 。每次询问给定评测机一个矩形的长和宽,评测机返回的是对长和宽分别用尺子测量后的结果的乘积。在 10 次询问以内找到遗失的刻度。

发现刻度 1 没有遗失,且尺子满足二段性,那么就可以定死,长为 1 ,宽用二分法进行枚举,判断一下返回的乘积是否正常进行边界移动即可。 log2(999)<10 。

二分。时间复杂度 O(log(Dom)) 。

#include <bits/stdc++.h>

using namespace std;
using ll = long long;

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);

  int tc;
  cin >> tc;
  while (tc--) {
    auto ans = *ranges::partition_point(views::iota(2, 1000), [](int x) {
      cout << format("? {} {}", 1, x) << endl;

      int res;
      cin >> res;

      return res <= x;
    });

    cout << format("! {}", ans) << endl;
  }

  return 0;
}

G2. Ruler (hard version)

题意同 G1 ,但是询问次数上限为 7 。

发现 log3(999)<7 ,于是考虑三分,每次缩小 2/3 的区间。每次询问给定当前区间的两个三分点,并根据返回结果判断该取三个区间中的哪一个即可。

三分。时间复杂度 O(log(Dom)) 。

#include <bits/stdc++.h>

using namespace std;
using ll = long long;

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);

  int tc;
  cin >> tc;
  while (tc--) {
    int l = 2, r = 999;
    while (l < r) {
      int m0 = l + (r - l) / 3;
      int m1 = r - (r - l) / 3;
      cout << format("? {} {}", m0, m1) << endl;

      int res;
      cin >> res;

      if (res == m0 * m1) {
        l = m1 + 1;
      } else if (res == m0 * (m1 + 1)) {
        l = m0 + 1, r = m1;
      } else {
        r = m0;
      }
    }
    cout << format("! {}", l) << endl;
  }

  return 0;
}

  • 12
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值