Atcoder Grand Contest 022 简要题解

本文提供了五道算法竞赛题目的详细解答过程,包括字符串处理、序列构造、动态规划等核心内容,通过具体实现展示了高效的算法设计技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Diverse Word

长度不是 2626 的时候,往后加第一个没出现过的字符就行了。

否则找到最大的一个 ii 满足存在 j>i,sj>si ,然后把最小的 sjsj 换过来就行了。

#include <bits/stdc++.h>

using namespace std;

#define X first
#define Y second
#define mp make_pair
#define pb push_back
#define Debug(...) fprintf(stderr, __VA_ARGS__)

typedef long long LL;
typedef long double LD;
typedef unsigned int uint;
typedef pair <int, int> pii;
typedef unsigned long long uLL;

template <typename T> inline void Read(T &x) {
  char c = getchar();
  bool f = false;
  for (x = 0; !isdigit(c); c = getchar()) {
    if (c == '-') {
      f = true;
    }
  }
  for (; isdigit(c); c = getchar()) {
    x = x * 10 + c - '0';
  }
  if (f) {
    x = -x;
  }
}

template <typename T> inline bool CheckMax(T &a, const T &b) {
  return a < b ? a = b, true : false;
}

template <typename T> inline bool CheckMin(T &a, const T &b) {
  return a > b ? a = b, true : false;
}

const int N = 30;

char s[N];
bool v[N];
int n;

int main() {
#ifdef wxh010910
  freopen("d.in", "r", stdin);
#endif
  scanf("%s", s), n = strlen(s);
  for (int i = 0; i < n; ++i) {
    v[s[i] - 'a'] = true;
  }
  if (n != 26) {
    for (int i = 0; i < 26; ++i) {
      if (!v[i]) {
        s[n] = i + 'a', puts(s);
        return 0;
      }
    }
  }
  for (int i = n - 1; ~i; --i) {
    for (int j = s[i] - 'a' + 1; j < 26; ++j) {
      if (!v[j]) {
        s[i] = j + 'a', s[i + 1] = 0, puts(s);
        return 0;
      }
    }
    v[s[i] - 'a'] = false;
  }
  puts("-1");
#ifdef wxh010910
  Debug("My Time: %.3lfms\n", (double)clock() / CLOCKS_PER_SEC);
#endif
  return 0;
}

GCD Sequence

小范围打表求出 1212 以内长度为 88 的构造,然后每次加 12 就行了。

#include <bits/stdc++.h>

using namespace std;

#define X first
#define Y second
#define mp make_pair
#define pb push_back
#define Debug(...) fprintf(stderr, __VA_ARGS__)

typedef long long LL;
typedef long double LD;
typedef unsigned int uint;
typedef pair <int, int> pii;
typedef unsigned long long uLL;

template <typename T> inline void Read(T &x) {
  char c = getchar();
  bool f = false;
  for (x = 0; !isdigit(c); c = getchar()) {
    if (c == '-') {
      f = true;
    }
  }
  for (; isdigit(c); c = getchar()) {
    x = x * 10 + c - '0';
  }
  if (f) {
    x = -x;
  }
}

template <typename T> inline bool CheckMax(T &a, const T &b) {
  return a < b ? a = b, true : false;
}

template <typename T> inline bool CheckMin(T &a, const T &b) {
  return a > b ? a = b, true : false;
}

int n, m;

int main() {
#ifdef wxh010910
  freopen("d.in", "r", stdin);
#endif
  Read(n);
  if (n == 3) {
    puts("2 5 63");
    return 0;
  }
  if (n & 1) {
    int a[8] = {6, 2, 10, 3, 9, 4, 8, 12};
    for (int i = 0; i < n; ++i) {
      printf("%d%c", a[i & 7], i == n - 1 ? '\n' : ' ');
      a[i & 7] += 12;
    }
  } else {
    int a[8] = {2, 10, 3, 9, 4, 8, 6, 12};
    for (int i = 0; i < n; ++i) {
      printf("%d%c", a[i & 7], i == n - 1 ? '\n' : ' ');
      a[i & 7] += 12;
    }
  }
#ifdef wxh010910
  Debug("My Time: %.3lfms\n", (double)clock() / CLOCKS_PER_SEC);
#endif
  return 0;
}

Remainder Game

逐位确定,每次建出转移图暴力判断是否可达。

#include <bits/stdc++.h>

using namespace std;

#define X first
#define Y second
#define mp make_pair
#define pb push_back
#define Debug(...) fprintf(stderr, __VA_ARGS__)

typedef long long LL;
typedef long double LD;
typedef unsigned int uint;
typedef pair <int, int> pii;
typedef unsigned long long uLL;

template <typename T> inline void Read(T &x) {
  char c = getchar();
  bool f = false;
  for (x = 0; !isdigit(c); c = getchar()) {
    if (c == '-') {
      f = true;
    }
  }
  for (; isdigit(c); c = getchar()) {
    x = x * 10 + c - '0';
  }
  if (f) {
    x = -x;
  }
}

template <typename T> inline bool CheckMax(T &a, const T &b) {
  return a < b ? a = b, true : false;
}

template <typename T> inline bool CheckMin(T &a, const T &b) {
  return a > b ? a = b, true : false;
}

const int N = 55;

int n, a[N], b[N];
LL ans, adj[N];
bool v[N];

inline bool Check() {
  for (int i = 0; i <= 50; ++i) {
    adj[i] = 1LL << i;
    for (int j = 1; j <= i; ++j) {
      if (v[j]) {
        adj[i] |= adj[i % j];
      }
    }
  }
  for (int i = 1; i <= n; ++i) {
    if (!(adj[a[i]] >> b[i] & 1)) {
      return false;
    }
  }
  return true;
}

int main() {
#ifdef wxh010910
  freopen("d.in", "r", stdin);
#endif
  Read(n);
  for (int i = 1; i <= n; ++i) {
    Read(a[i]);
  }
  for (int i = 1; i <= n; ++i) {
    Read(b[i]);
  }
  for (int i = 1; i <= 50; ++i) {
    v[i] = true;
  }
  if (!Check()) {
    puts("-1");
    return 0;
  }
  for (int i = 50; i; --i) {
    v[i] = false;
    if (!Check()) {
      v[i] = true, ans += 1LL << i;
    }
  }
  printf("%lld\n", ans);
#ifdef wxh010910
  Debug("My Time: %.3lfms\n", (double)clock() / CLOCKS_PER_SEC);
#endif
  return 0;
}

Shopping

实际上是要最小化车往返次数,首先有一种策略就是按照编号从小到大访问,考虑优化这个策略,记 li=[ti2xi]li=[ti≤2xi] ,即从右边进去能否从右边出来, ri=[ti2(Lxi)]ri=[ti≤2(L−xi)] ,即从左边进去能否从左边出来,那么对于 i<j,li=rj=1i<j,li=rj=1 ,可以通过先走 jj 再走 i 的方式减少一次次数,然后就是类似于求一个匹配,贪心即可,注意要特判要经过所有点。

#include <bits/stdc++.h>

using namespace std;

#define X first
#define Y second
#define mp make_pair
#define pb push_back
#define Debug(...) fprintf(stderr, __VA_ARGS__)

typedef long long LL;
typedef long double LD;
typedef unsigned int uint;
typedef pair <int, int> pii;
typedef unsigned long long uLL;

template <typename T> inline void Read(T &x) {
  char c = getchar();
  bool f = false;
  for (x = 0; !isdigit(c); c = getchar()) {
    if (c == '-') {
      f = true;
    }
  }
  for (; isdigit(c); c = getchar()) {
    x = x * 10 + c - '0';
  }
  if (f) {
    x = -x;
  }
}

template <typename T> inline bool CheckMax(T &a, const T &b) {
  return a < b ? a = b, true : false;
}

template <typename T> inline bool CheckMin(T &a, const T &b) {
  return a > b ? a = b, true : false;
}

const int N = 300005;

int n, m, x, y, a[N];
bool l[N], r[N];
LL ans;

int main() {
#ifdef wxh010910
  freopen("d.in", "r", stdin);
#endif
  Read(n), Read(m);
  for (int i = 1; i <= n; ++i) {
    Read(a[i]);
  }
  for (int i = 1, t; i <= n; ++i) {
    Read(t);
    if (t % (m << 1) == 0) {
      ans += t;
    } else {
      ans += 1LL * (t / (m << 1) + 1) * (m << 1), t %= m << 1;
      if (t <= a[i] << 1) {
        l[i] = true;
      }
      if (t <= m - a[i] << 1) {
        r[i] = true;
      }
    }
  }
  for (int i = 1; i < n; ++i) {
    if (x && r[i]) {
      if (l[i]) {
        ++y;
      }
      --x, ans -= m << 1;
    } else if (!l[i] && r[i]) {
      if (y) {
        --y, ++x;
      }
    } else if (l[i]) {
      ++x;
    }
  }
  if (!r[n]) {
    ans += m << 1;
  }
  printf("%lld\n", ans);
#ifdef wxh010910
  Debug("My Time: %.3lfms\n", (double)clock() / CLOCKS_PER_SEC);
#endif
  return 0;
}

Median Replace

考虑合法的序列长什么样:往序列最后添加一个 11 ,记 ai 表示第 ii1 和第 i1i−111 之间 0 的个数,那么对于 ai3ai≥3 ,可以不断操作让 aiai 减去 22 ,所以之后认为 ai2

考虑操作对 aiai 的影响,只有两种情况:将两个相邻 00 合并成一个 0 或者将相邻的 x,yx,y 合并成 x+y1x+y−1 ,不难发现 ai=1ai=1 是没有用的,可以直接去掉。而合并两个偶数一定会变成一个奇数,相当于删除两个相邻的数。最后序列要变成 0,00,0 ,那么就是找到一对 (i,j)(i,j) 满足 ai=aj=0ai=aj=0 ,并且 ii 前面和 j 后面都有偶数个元素。

这样就可以用一个DP求出合法的序列个数了。

#include <bits/stdc++.h>

using namespace std;

#define X first
#define Y second
#define mp make_pair
#define pb push_back
#define Debug(...) fprintf(stderr, __VA_ARGS__)

typedef long long LL;
typedef long double LD;
typedef unsigned int uint;
typedef pair <int, int> pii;
typedef unsigned long long uLL;

template <typename T> inline void Read(T &x) {
  char c = getchar();
  bool f = false;
  for (x = 0; !isdigit(c); c = getchar()) {
    if (c == '-') {
      f = true;
    }
  }
  for (; isdigit(c); c = getchar()) {
    x = x * 10 + c - '0';
  }
  if (f) {
    x = -x;
  }
}

template <typename T> inline bool CheckMax(T &a, const T &b) {
  return a < b ? a = b, true : false;
}

template <typename T> inline bool CheckMin(T &a, const T &b) {
  return a > b ? a = b, true : false;
}

const int N = 300005;
const int mod = 1e9 + 7;

int n, ans, suf[N], f[N][2][2][3];
char s[N];

int main() {
#ifdef wxh010910
  freopen("d.in", "r", stdin);
#endif
  scanf("%s", s), n = strlen(s), f[0][0][0][0] = suf[n] = 1;
  for (int i = n - 1; ~i; --i) {
    suf[i] = 1LL * suf[i + 1] * ((s[i] == '?') + 1) % mod;
  }
  for (int i = 0; i < n; ++i) {
    if (s[i] != '1') {
      for (int j = 0; j < 2; ++j) {
        for (int k = 0; k < 2; ++k) {
          for (int l = 0; l < 3; ++l) {
            f[i + 1][j][k][(l & 1) + 1] = (f[i + 1][j][k][(l & 1) + 1] + f[i][j][k][l]) % mod;
          }
        }
      }
    }
    if (s[i] != '0') {
      for (int j = 0; j < 2; ++j) {
        for (int k = 0; k < 2; ++k) {
          if (!j && !k) {
            f[i + 1][1][1][0] = (f[i + 1][1][1][0] + f[i][j][k][0]) % mod;
          } else if (j && k) {
            ans = (1LL * f[i][j][k][0] * suf[i + 1] + ans) % mod;
          } else {
            f[i + 1][!j][k][0] = (f[i + 1][!j][k][0] + f[i][j][k][0]) % mod;
          }
          f[i + 1][j][k][0] = (f[i + 1][j][k][0] + f[i][j][k][1]) % mod;
          f[i + 1][!j][k][0] = (f[i + 1][!j][k][0] + f[i][j][k][2]) % mod;
        }
      }
    }
  }
  printf("%d\n", (ans + f[n][1][1][0]) % mod);
#ifdef wxh010910
  Debug("My Time: %.3lfms\n", (double)clock() / CLOCKS_PER_SEC);
#endif
  return 0;
}

Checkers

因为 XX 很大,可以认为是 n 维向量进行运算,因为向量每一维是什么并不重要,只要维护每一维值的集合就行了。不难发现,每一维的值一定是 2i2i 或者 2i−2i ,并且每个集合值的总和为 11 ,且满足 11−1 出现次数总和恰好为 11

以上条件是不够的,事实上还需要一个条件:对于任意 i ,可以通过调整绝对值为 2i2i 的符号来让绝对值不超过 2i2i 的数和为 11 ,必要性显然,充分性可以考虑归纳法,对于一个集合 S ,如果能分成 2A2AB−BA,BA,B 也满足这个条件,那就证完了。

不妨设 1S−1∈S ,记 kk 为最小的出现过的 2k ,如果对于 1i<k1≤i<k2i−2i 出现过至少两次,那么可以构造 A={20,21,22,,2k2,2k1}A={−20,−21,−22,⋯,−2k−2,2k−1} ,可以验证满足条件。否则记最小的只出现过一次的是 2i−2i ,如果 i=1i=1 ,那么 B={20}B={20} 也满足条件,否则 12i1j=12j<2i1−1−2∑j=1i−12j<−2i−1 ,就不是合法的集合了。另一种情况同理。之后DP很简单就不细说了。

#include <bits/stdc++.h>

using namespace std;

#define X first
#define Y second
#define mp make_pair
#define pb push_back
#define Debug(...) fprintf(stderr, __VA_ARGS__)

typedef long long LL;
typedef long double LD;
typedef unsigned int uint;
typedef pair <int, int> pii;
typedef unsigned long long uLL;

template <typename T> inline void Read(T &x) {
  char c = getchar();
  bool f = false;
  for (x = 0; !isdigit(c); c = getchar()) {
    if (c == '-') {
      f = true;
    }
  }
  for (; isdigit(c); c = getchar()) {
    x = x * 10 + c - '0';
  }
  if (f) {
    x = -x;
  }
}

template <typename T> inline bool CheckMax(T &a, const T &b) {
  return a < b ? a = b, true : false;
}

template <typename T> inline bool CheckMin(T &a, const T &b) {
  return a > b ? a = b, true : false;
}

const int N = 105;
const int mod = 1e9 + 7;

int n, c[N][N], f[N][N];

int main() {
#ifdef wxh010910
  freopen("d.in", "r", stdin);
#endif
  Read(n);
  for (int i = 0; i <= n; ++i) {
    c[i][0] = 1;
    for (int j = 1; j <= i; ++j) {
      c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % mod;
    }
  }
  f[1][n] = f[1][n - 1] = n;
  for (int i = 1; i <= n; ++i) {
    for (int j = -n; j <= n; ++j) {
      if (f[i][j + n]) {
        for (int k = max(1, abs(j)); k <= n - i; ++k) {
          if (!(j + k & 1)) {
            for (int l = 0; l <= k; ++l) {
              int t = (j + k >> 1) - l;
              if (t >= -n && t <= n) {
                f[i + k][t + n] = (1LL * f[i][j + n] * c[n - i][k] % mod * c[k][l] + f[i + k][t + n]) % mod;
              }
            }
          }
        }
      }
    }
  }
  printf("%d\n", f[n][n]);
#ifdef wxh010910
  Debug("My Time: %.3lfms\n", (double)clock() / CLOCKS_PER_SEC);
#endif
  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值