Loading

AtCoder Beginner Contest 473 (ABC473) 解题报告

很简单的一场。

ABC473A

signed main(){
  ios::sync_with_stdio(0), cin.tie(0);
  
  int n, ans = 0;
  cin >> n;
  for(int i = 1; i <= n; i ++){
    int x; cin >> x;
    if(i > n / 2) ans += x;
  }

  cout << ans << "\n";
  
  return 0;
}

ABC473B

统计所有出现奇数次的数的和。

int n, cnt[107], ans;

signed main(){
  ios::sync_with_stdio(0), cin.tie(0);
  
  cin >> n;
  for(int i = 1; i <= n; i ++){
    int x; cin >> x;
    cnt[x] ++;
  }

  for(int i = 1; i <= 100; i ++){
    ans += (cnt[i] & 1) * i;
  }
  cout << ans << "\n";
  
  return 0;
}

ABC473C

只有出现次数最多,或者比最多少 \(1\) 的班级合法。

const int N = 2e5 + 7;
int n, k, cnt[N];
set<int> st;
int fst, snd;

signed main(){
  ios::sync_with_stdio(0), cin.tie(0);
  
  cin >> n >> k;
  for(int i = 1; i <= n; i ++){
    int x; cin >> x;
    cnt[x] ++;
  }
  for(int i = 1; i <= n; i ++){
    st.insert(cnt[i]);
  }

  fst = *st.rbegin();

  int ans = 0;
  for(int i = 1; i <= n; i ++){
    ans += (cnt[i] == fst || cnt[i] == fst - 1);
  }
  cout << ans << "\n";
  
  return 0;
}

ABC473D

暴力 DFS 加上对 \(x = n\) 时的 \(\mathcal{O}(1)\) 计算即可通过。

int n, k, a[17];

void dfs(int x, int rem){
  if(x == n && (rem % n)) return;
  if(x == n){
    a[x] = rem / x;
    for(int i = 1; i <= n; i ++) cout << a[i] << " ";
    cout <<"\n"; return;
  }
  for(int i = 0; i * x <= rem; i ++){
    a[x] = i, dfs(x + 1, rem - i * x);
  }
}

调用 dfs(1, k)

ABC473E

题目是想让你把一个数组分为若干段,一种方案的得分,是 \(k | sum\) 的段数,求最大得分。

这是个简单的 DP。首先肯定有 \(f_i\) 代表前 \(i\) 个的最大得分。但是这样是 \(\mathcal{O}(n^2)\)。题意等价于:找出尽可能多个互不相交的段,使得每段 \(k | sum\)。那么重新定义 \(f_i\) 代表前 \(i\) 个内的答案,显然有转移

\[f_i \stackrel{\max}{\longleftarrow} f_{i - 1} \]

\[f_i \stackrel{\max}{\longleftarrow} f_j + 1, \text{sum}(j + 1, i) \equiv k \pmod k \]

\(\text{sum}(l, r)\) 代表区间和,对于后者,我们拿 std::map 维护每个前缀和下的最大 \(f\) 值即可,答案为 \(f_n\)

const int N = 2e5 + 7;
int n, k, a[N], f[N];
map<int, int> mx;

signed main(){
  ios::sync_with_stdio(0), cin.tie(0);
  
  cin >> n >> k;
  for(int i = 1; i <= n; i ++){
    cin >> a[i];
    a[i] += a[i - 1];
    a[i] %= k;
  }

  mx[0] = 0;
  for(int i = 1; i <= n; i ++){
    f[i] = f[i - 1];
    if(mx.count(a[i])) f[i] = max(f[i], mx[a[i]] + 1);
    mx[a[i]] = max(mx[a[i]], f[i]);
  }

  cout << f[n] << "\n";
  
  return 0;
}

ABC473F

题意是,字符串单点修改,查询区间字串是否能由一个空串反复插入 AAB 得到。

你会发现满足条件的区间中,A 的个数一定不少于 B,并且 \([l, r]\) 的每个前缀都要满足这个条件。

如果你做过的题足够多,你会发现这是一个括号匹配。将 AB 分别赋值为 \(1\)\(-1\),你会发现条件其实等价于 \([l, r]\) 的每个前缀和都要非负,所以 \([l, r]\) 中前缀和的最小值决定其是否合法。

那只需要维护整个数组的前缀和即可。使用一棵区间加,区间求最小值,求和的线段树即可。

struct sgt {
  void add(int x, int l, int r, int ql, int qr, int k) {}
  int qsum(int x, int l, int r, int ql, int qr) {}
  int qmin(int x, int l, int r, int ql, int qr) {}
} SGT;

signed main(){
  ios::sync_with_stdio(0), cin.tie(0);
  
  int n; string s; cin >> n >> s;
  s = "o" + s;
  for(int i = 1; i <= n; i ++){
    if(s[i] == 'A') SGT.add(1, 1, n, i, n, 1);
    else SGT.add(1, 1, n, i, n, -1);
  }

  map<char, int> mp;
  mp['A'] = 1, mp['B'] = -1;
  int q; cin >> q;
  while(q --){
    int opt, x; cin >> opt >> x;
    if(opt == 1){
      char y; cin >> y;
      SGT.add(1, 1, n, x, n, -mp[s[x]]);
      s[x] = y;
      SGT.add(1, 1, n, x, n, mp[s[x]]);
    }
    else{
      int y; cin >> y;
      int pre = (x > 1 ? SGT.qsum(1, 1, n, x - 1, x - 1) : 0);
      int mn = SGT.qmin(1, 1, n, x, y) - pre; // [x, y] 上的最小前缀和
      cout << (mn >= 0 ? "Yes\n" : "No\n");
    }
  }
  
  return 0;
}

ABC473G

题意是,有 \(n\) 张正面朝下的牌,数字为 \([1, n]\),现在你不知道每张牌的具体数字。每次你可以选择翻一张牌。如果比当前牌小的牌全都被拿走,这张牌也可以被拿走。

特别地,第一张牌可以直接拿走。求在最优策略下,使用 \(v\) 步翻完所有牌的概率。

然后考虑一下这个所谓的最优策略是啥:显然是如果当前的牌符合条件就直接拿走,否则记住这个位置,下次能拿就拿。

那么肯定是,一个牌最多贡献两次操作数。

并且,我们还发现,最优策略下的操作数一定处于 \([n, 2n - 1]\) 之间。假设初始操作数为 \(n\) 的情况下。假设我们还有 \(s\) 张牌不知道是什么,那么不论现在你已经拿走了多少张牌,下一次操作时,你肯定有 \(\frac{s - 1}{s}\) 的概率使得操作数 \(+1\),有 \(\frac{1}{s}\) 的概率操作数不变。

这样,如果我们的目标操作数是 \(v = n + k\),那么在前 \(n\) 次操作中(其实前 \(n\) 次操作结束后我们肯定已经知道所有牌上的数了),必须有 \(k\) 次选到了 \(\frac{s - 1}{s}\),另外 \(n - k\) 次选到 \(\frac{1}{s}\)。我们让 \(h(i, 0) = \frac{1}{i}, h(i, 1) = \frac{i - 1}{i}\)。定义一个 \(01\) 数组 \(g\)\(\text{count(g)}\) 代表 \(g\)\(1\) 的个数。我们其实是想求

\[\sum_{\text{count}(g) = k} \prod_i h(i, g_i) \]

然后推式子,定义 \(z(i, 0) = 1, z(i, 1) = \frac{h(i, 1)}{h(i, 0)} = i - 1\),则上述式子等于

\[\sum_{\text{count}(g) = k} \prod_i h(i, 0)z(i, g_i) \]

进一步化简可得

\[\prod_i h(i, 0) \times (\sum_{\text{count}(g) = k} \prod_{g_i = 1} z(i, 1)) \]

其实到这一步就很明显了,显然 \(\prod_i h(i, 0)\)\(g\) 数组无关,那么其实我们想要计算的其实是 \(z_{*, 1}\) 数组的所有 \(k\) 子集的乘积之和。

推到这一步之后应该很可做了但是我不会,问了一下 DeepSeek 说是可以使用 NTT 之类的科技来求,就可以达到一个较低的复杂度了。

提供一个思路,代码就不给了。

posted @ 2026-08-30 00:01  Trent900  阅读(118)  评论(0)    收藏  举报