AGC039 简要题解

头一回凭自己本事把一整场AGC的题都补了,补完发现有些题做法和官方做法不太一样,所以还是有些记录价值的。

A - Connection and Disconnection

显然总的来说每个长为 L L L 的连续段恰改掉 ⌊ L 2 ⌋ \left\lfloor \frac L2 \right\rfloor 2L 个字符,为了计算这一总和再看看是否首位相等,整段相等。

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <ctime>
#include <cctype>
 
#include <algorithm>
#include <random>
#include <bitset>
#include <queue>
#include <functional>
#include <set>
#include <map>
#include <vector>
#include <chrono>
#include <iostream>
#include <limits>
#include <numeric>
 
#define LOG(FMT...) fprintf(stderr, FMT)

using namespace std;

typedef long long ll;
typedef unsigned long long ull;

// mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());

const int N = 110;

char s[N];

int main() {
  int n, k;
  scanf("%s%d", s + 1, &k);
  n = strlen(s + 1);
  if (count(s + 1, s + n + 1, s[1]) == n)
    printf("%lld\n", n * (ll)k / 2);
  else {
    ll ans = 0;
    if (s[1] != s[n]) {
      char c = s[1];
      int cnt = 1;
      for (int i = 2; i <= n + 1; ++i)
        if (c == s[i])
          ++cnt;
        else {
          ans += cnt / 2LL * k;
          cnt = 1;
          c = s[i];
        }
    } else {
      int l = 1, r = n;
      while (s[l] == s[1]) ++l;
      while (s[r] == s[n]) --r;
      char c = s[l];
      int cnt = 1;
      for (int i = l + 1; i <= r + 1; ++i)
        if (c == s[i])
          ++cnt;
        else {
          ans += cnt / 2LL * k;
          cnt = 1;
          c = s[i];
        }
      ans += (l - 1 + n - r) / 2LL * (k - 1);
      ans += (l - 1) / 2;
      ans += (n - r) / 2;
    }
    printf("%lld\n", ans);
  }
  return 0;
}

B - Graph Partition

显然必须是二分图,其次不难发现找到一个离所有点最长的最短路,最小化这一值就是答案,跑一遍 Floyd 即可。

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <ctime>
#include <cctype>
 
#include <algorithm>
#include <random>
#include <bitset>
#include <queue>
#include <functional>
#include <set>
#include <map>
#include <vector>
#include <chrono>
#include <iostream>
#include <limits>
#include <numeric>
 
#define LOG(FMT...) fprintf(stderr, FMT)

using namespace std;

typedef long long ll;
typedef unsigned long long ull;

// mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());

const int N = 210;

int n;
char s[N][N];
int d[N][N];
int vis[N];

void dfs(int u) {
  for (int v = 1; v <= n; ++v)
    if (s[u][v] == '1')
      if (!vis[v]) {
        vis[v] = -vis[u];
        dfs(v);
      } else if (vis[v] == vis[u]) {
        puts("-1");
        exit(0);
      }
}

int main() {
  scanf("%d", &n);
  for (int i = 1; i <= n; ++i)
    scanf("%s", s[i] + 1);
  vis[1] = 1;
  dfs(1);
  for (int i = 1; i <= n; ++i)
    fill(d[i] + 1, d[i] + n + 1, n);
  for (int i = 1; i <= n; ++i)
    d[i][i] = 0;
  for (int i = 1; i <= n; ++i)
    for (int j = 1; j <= n; ++j)
      if (s[i][j] == '1')
        d[i][j] = 1;
  for (int i = 1; i <= n; ++i)
    for (int j = 1; j <= n; ++j)
      for (int k = 1; k <= n; ++k)
        d[j][k] = min(d[j][k], d[j][i] + d[i][k]);
  int ans = 0;
  for (int i = 1; i <= n; ++i)
    ans = max(ans, *max_element(d[i] + 1, d[i] + n + 1));
  printf("%d\n", ans + 1);
  return 0;
}

C - Division by Two with Something

通过循环节容斥。

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <ctime>
#include <cctype>
 
#include <algorithm>
#include <random>
#include <bitset>
#include <queue>
#include <functional>
#include <set>
#include <map>
#include <vector>
#include <chrono>
#include <iostream>
#include <limits>
#include <numeric>
 
#define LOG(FMT...) fprintf(stderr, FMT)

using namespace std;

typedef long long ll;
typedef unsigned long long ull;

// mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());

const int N = 200010, P = 998244353;

int n;
int sieve[N * 2];
bool vs[N];
char s[N];

int mpow(int x, int k) {
  int ret = 1;
  while (k) {
    if (k & 1)
      ret = ret * (ll)x % P;
    x = x * (ll)x % P;
    k >>= 1;
  }
  return ret;
}

int norm(int x) { return x >= P ? x - P : x; }

void add(int& x, int y) {
  if ((x += y) >= P)
    x -= P;
}

void sub(int& x, int y) {
  if ((x -= y) < 0)
    x += P;
}

int gcd(int a, int b) { return b ? gcd(b, a % b) : a; }

int solve(int x) {
  if (x / gcd(x, n) % 2)
    return 0;
  int p = 0;
  bool cur = false;
  int cnt = 0;
  do {
    vs[p] = cur;
    cur ^= ((p + x) / n) & 1;
    p = (p + x) % n;
    ++cnt;
  } while (p != 0);
  int sz = n / cnt, ret = 0;
  for (int i = 1; i <= sz; ++i)
    ret = (ret * 2 + s[n - i]) % P;
  for (int i = n - 1; i; --i) {
    int p = i % sz;
    bool dig = s[n - sz + p] ^ vs[i - p] ^ vs[n - sz];
    if (dig > s[i])
      return ret;
    if (dig < s[i])
      return norm(ret + 1);
  }
  return norm(ret + 1);
}

int main() {
  scanf("%d%s", &n, s);
  reverse(s, s + n);
  for (int i = 0; i < n; ++i)
    s[i] -= '0';
  for (int i = 1; i <= n * 2; ++i)
    if (n * 2 % i == 0)
      sieve[i] = i;
  for (int i = n * 2; i; --i)
    for (int j = i + i; j <= n * 2; j += i)
      sub(sieve[i], sieve[j]);
  int ans = 0;
  for (int i = 1; i <= n * 2; ++i)
    if (sieve[i]) {
      ans = (ans + sieve[i] * (ll)solve(i)) % P;
    }
  printf("%d\n", ans);
  return 0;
}

D - Incenters

数学题怎么回事
考虑本题的重要条件是所有点都分布在同一个圆上,不难通过倒弧和角得到一个好用的结论:中心就是一个角平分线打到对面的交点处为圆心,以这一圆心到另外两点(显然距离相等)为半径画圆,和原来那条角平分线的交点。进一步选一个点的时候这个东西可以把每个点做出的圆的一个向量集体做同一个线性变换,因此可以 Θ ( n 2 ) \Theta(n^2) Θ(n2) 计算。

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <ctime>
#include <cctype>

#include <algorithm>
#include <complex>
#include <random>
#include <bitset>
#include <queue>
#include <functional>
#include <set>
#include <map>
#include <vector>
#include <chrono>
#include <iostream>
#include <limits>
#include <numeric>

#define LOG(FMT...) fprintf(stderr, FMT)

using namespace std;

typedef long long ll;
typedef unsigned long long ull;

// mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());

template <class T>
istream& operator>>(istream& is, vector<T>& v) {
  for (T& x : v)
    is >> x;
  return is;
}

template <class T>
ostream& operator<<(ostream& os, const vector<T>& v) {
  if (!v.empty()) {
    os << v.front();
    for (int i = 1; i < v.size(); ++i)
      os << ' ' << v[i];
  }
  return os;
}

typedef complex<double> C;

const double PI = acos(-1.);

int main() {
#ifdef LBT
  freopen("test.in", "r", stdin);
  int nol_cl = clock();
#endif
  ios::sync_with_stdio(false);
  cin.tie(nullptr);

  int n, l;
  cin >> n >> l;
  vector<int> t(n);
  cin >> t;
  vector<double> theta(n);
  transform(t.begin(), t.end(), theta.begin(), [&](int t) { return 2 * PI * t / l; });
  function<C(double)> unit = [](double theta) {
    return C(cos(theta), sin(theta));
  };
  C ans = 0;
  C sum = 0;
  for (int i = 1; i < n - 1; ++i) {
    sum += unit(theta[i - 1] * .5);
    for (int j = i + 1; j < n; ++j) {
      C center = unit((theta[i] + theta[j]) * .5);
      double r = sin((theta[j] - theta[i]) * .25) * 2;
      C relative = sum * unit((theta[i] + theta[j]) * .25 - PI * .5);
      ans += center * (double)i + r * relative;
    }
  }
  ans /= n * (n - 1) * (n - 2LL) / 6;
  printf("%.15f %.15f\n", ans.real(), ans.imag());

#ifdef LBT
  LOG("Time: %dms\n", int ((clock()
          -nol_cl) / (double)CLOCKS_PER_SEC * 1000));
#endif
  return 0;
}

E - Pairing Points

随便 DP 搞搞,重点状态 f ( l , m , r ) f(l, m, r) f(l,m,r) 表示外部向 [ l , r ] [l, r] [l,r] 内连进来了一条边接到 m ( l ≤ m ≤ r ) m (l\le m\le r) m(lmr) 点,显见拆掉这条边之后就是两边若干个“哑铃”形状。优化一下计算顺序可以做到 Θ ( n 5 ) \Theta(n^5) Θ(n5),感觉官方的 Θ ( n 7 ) \Theta(n^7) Θ(n7) 太不走心了。

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cstring>

#include <algorithm>
#include <numeric>
#include <functional>
#include <vector>

#define LOG(FMT...) fprintf(stderr, FMT)

using namespace std;

typedef long long ll;

const int N = 41;

int n;
char a[N][N];
ll dp[N][N][N], dp2[N][N][N][N], dp3[N][N][N];

ll dfs2(int l1, int r1, int l2, int r2);

ll dfs(int l, int m, int r) {
  if (dp[l][m][r] != -1)
    return dp[l][m][r];
  if (l == r)
    return dp[l][m][r] = 1;
  dp[l][m][r] = 0;
  for (int i = l; i < m; i += 2)
    for (int j = r; j > m; j -= 2)
      dp[l][m][r] += dfs2(l, i, j, r) * dfs(i + 1, m, j - 1);
  return dp[l][m][r];
}

ll dfs3(int p, int l, int r) {
  if (dp3[p][l][r] != -1)
    return dp3[p][l][r];
  dp3[p][l][r] = 0;
  for (int i = l; i <= r; ++i)
    if (a[p][i])
      dp3[p][l][r] += dfs(l, i, r);
  return dp3[p][l][r];
}

ll dfs2(int l1, int r1, int l2, int r2) {
  if (dp2[l1][r1][l2][r2] != -1)
    return dp2[l1][r1][l2][r2];
  dp2[l1][r1][l2][r2] = 0;
  for (int i = l1; i <= r1; ++i)
    dp2[l1][r1][l2][r2] += dfs(l1, i, r1) * dfs3(i, l2, r2);
  return dp2[l1][r1][l2][r2];
}

int main() {
#ifdef LBT
  freopen("test.in", "r", stdin);
#endif

  scanf("%d", &n);
  for (int i = 0; i < n * 2; ++i)
    scanf("%s", a[i]);
  for (int i = 0; i < n * 2; ++i)
    for (int j = 0; j < n * 2; ++j)
      a[i][j] -= '0';
  
  memset(dp, -1, sizeof(dp));
  memset(dp2, -1, sizeof(dp2));
  memset(dp3, -1, sizeof(dp3));
  printf("%lld\n", dfs3(0, 1, n * 2 - 1));

  return 0;
}

F - Min Product Sum

考虑把问题看成从小往大往里面放数,每次放进去一个数当行当列没被确定最小值的数此时就被确定了。然后是一个类似于 CTS2019 D1T1 的容斥,直接看是 Θ ( K N 3 M 3 ) \Theta(KN^3M^3) Θ(KN3M3) 的,但是发现枚举多几行几列被选和几行几列容斥掉可以分步做,也就是当成负数个选择方法,这样就是 Θ ( K N 2 M 2 ) \Theta(KN^2M^2) Θ(KN2M2) 的。然后发现把一些 i a b i^{ab} iab 类似的部分分开剩下的是两个维度个卷上 e x e y \mathrm{e}^x\mathrm{e}^y exey,容斥过来是 e − x e − y \mathrm{e}^{-x}\mathrm{e}^{-y} exey,这部分做一维乘法暴力就是 Θ ( K N M ( N + M ) ) \Theta(KNM(N+M)) Θ(KNM(N+M))

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <ctime>
#include <cctype>

#include <algorithm>
#include <random>
#include <bitset>
#include <queue>
#include <functional>
#include <set>
#include <map>
#include <vector>
#include <chrono>
#include <iostream>
#include <limits>
#include <numeric>

#define LOG(FMT...) fprintf(stderr, FMT)

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef vector<int> vi;

// mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());

template<class T>
istream &operator>>(istream &is, vector<T> &v) {
  for (T &x : v)
    is >> x;
  return is;
}

ostream &operator<<(ostream &os, const pair<char, int> &unit) {
  return os << unit.first << "^" << unit.second;
}

template<class T>
ostream &operator<<(ostream &os, const vector<T> &v) {
  if (!v.empty()) {
    os << v.front();
    for (int i = 1; i < v.size(); ++i)
      os << ' ' << v[i];
  }
  return os;
}

const int N = 110;

int P;
int fac[N], ifac[N], nifac[N], pw1[N * N], pw2[N * N];

int mpow(int x, int k) {
  int ret = 1;
  while (k) {
    if (k & 1)
      ret = ret * (ll) x % P;
    k >>= 1;
    x = x * (ll) x % P;
  }
  return ret;
}

int norm(int x) { return x >= P ? x - P : x; }

void add(int &x, int y) {
  if ((x += y) >= P)
    x -= P;
}

void sub(int &x, int y) {
  if ((x -= y) < 0)
    x += P;
}

void exGcd(int a, int b, int &x, int &y) {
  if (!b) {
    x = 1;
    y = 0;
    return;
  }
  exGcd(b, a % b, y, x);
  y -= a / b * x;
}

int inv(int a) {
  int x, y;
  exGcd(a, P, x, y);
  return norm(x + P);
}

void prepare(int n) {
  fac[0] = 1;
  for (int i = 1; i <= n; ++i) fac[i] = fac[i - 1] * (ll) i % P;
  ifac[1] = 1;
  for (int i = 2; i <= n; ++i)
    ifac[i] = -(P / i) * (ll) ifac[P % i] % P + P;
  ifac[0] = 1;
  for (int i = 1; i <= n; ++i)
    ifac[i] = ifac[i - 1] * (ll) ifac[i] % P;
  memcpy(nifac, ifac, sizeof(ifac));
  for (int i = 1; i <= n; i += 2)
    nifac[i] = P - nifac[i];
}

void gpw(int* pw, int x, int l) {
  pw[0] = 1;
  for (int k = 1; k <= l; ++k)
    pw[k] = pw[k - 1] * (ll)x % P;
}

int f[N][N];

int main() {
#ifdef LBT
  freopen("test.in", "r", stdin);
  int nol_cl = clock();
#endif
  ios::sync_with_stdio(false);
  cin.tie(nullptr);

  int n, m, k;
  cin >> n >> m >> k >> P;
  prepare(max(n, m));
  f[0][0] = 1;
  for (int i = 1; i <= k; ++i) {
    gpw(pw1, inv(i), n * m);
    gpw(pw2, inv(k - i + 1), n * m);
    for (int a = 0; a <= n; ++a)
      for (int b = 0; b <= m; ++b)
        f[a][b] = f[a][b] * (ll) pw1[a * m + b * n - a * b] % P * fac[n - a] % P * fac[m - b] % P * pw2[a * b] % P;
    for (int a = 0; a <= n; ++a)
      for (int b = m; b; --b)
        for (int j = 1; j <= b; ++j)
          f[a][b] = (f[a][b] + f[a][b - j] * (ll)ifac[j]) % P;
    for (int b = 0; b <= m; ++b)
      for (int a = n; a; --a)
        for (int j = 1; j <= a; ++j)
          f[a][b] = (f[a][b] + f[a - j][b] * (ll)ifac[j]) % P;
    gpw(pw2, k - i + 1, n * m);
    for (int a = 0; a <= n; ++a)
      for (int b = 0; b <= m; ++b)
        f[a][b] = f[a][b] * (ll)pw2[a * b] % P;
    if (i == k) {
      int ans = f[n][m] * (ll)mpow(i, n * m) % P;
      cout << ans;
      break;
    }

    gpw(pw1, i, n * m);
    gpw(pw2, inv(k - i), n * m);
    for (int a = 0; a <= n; ++a)
      for (int b = 0; b <= m; ++b)
        f[a][b] = f[a][b] * (ll)pw2[a * b] % P;
    for (int a = 0; a <= n; ++a)
      for (int b = m; b; --b)
        for (int j = 1; j <= b; ++j)
          f[a][b] = (f[a][b] + f[a][b - j] * (ll)nifac[j]) % P;
    for (int b = 0; b <= m; ++b)
      for (int a = n; a; --a)
        for (int j = 1; j <= a; ++j)
          f[a][b] = (f[a][b] + f[a - j][b] * (ll)nifac[j]) % P;
    gpw(pw2, k - i, n * m);
    for (int c = 0; c <= n; ++c)
      for (int d = 0; d <= m; ++d)
        f[c][d] = f[c][d] * (ll) pw1[c * m + d * n - c * d] % P * ifac[n - c] % P * ifac[m - d] % P * pw2[c * d] % P;
  }

#ifdef LBT
  LOG("Time: %dms\n", int ((clock()
          -nol_cl) / (double)CLOCKS_PER_SEC * 1000));
#endif
  return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
AtCoder Beginner Contest 134 是一场 AtCoder 的入门级比赛,以下是每道题的简要题解: A - Dodecagon 题目描述:已知一个正十二边形的边长,求它的面积。 解题思路:正十二边形的内角为 $150^\circ$,因此可以将正十二边形拆分为 12 个等腰三角形,通过三角形面积公式计算面积即可。 B - Golden Apple 题目描述:有 $N$ 个苹果和 $D$ 个盘子,每个盘子最多可以装下 $2D+1$ 个苹果,求最少需要多少个盘子才能装下所有的苹果。 解题思路:每个盘子最多可以装下 $2D+1$ 个苹果,因此可以将苹果平均分配到每个盘子中,可以得到最少需要 $\lceil \frac{N}{2D+1} \rceil$ 个盘子。 C - Exception Handling 题目描述:给定一个长度为 $N$ 的整数序列 $a$,求除了第 $i$ 个数以外的最大值。 解题思路:可以使用两个变量 $m_1$ 和 $m_2$ 分别记录最大值和次大值。遍历整个序列,当当前数不是第 $i$ 个数时,更新最大值和次大值。因此,最后的结果应该是 $m_1$ 或 $m_2$ 中较小的一个。 D - Preparing Boxes 题目描述:有 $N$ 个盒子和 $M$ 个物品,第 $i$ 个盒子可以放入 $a_i$ 个物品,每个物品只能放在一个盒子中。现在需要将所有的物品放入盒子中,每次操作可以将一个盒子内的物品全部取出并分配到其他盒子中,求最少需要多少次操作才能完成任务。 解题思路:首先可以计算出所有盒子中物品的总数 $S$,然后判断是否存在一个盒子的物品数量大于 $\lceil \frac{S}{2} \rceil$,如果存在,则无法完成任务。否则,可以用贪心的思想,每次从物品数量最多的盒子中取出一个物品,放入物品数量最少的盒子中。因为每次操作都会使得物品数量最多的盒子的物品数量减少,而物品数量最少的盒子的物品数量不变或增加,因此这种贪心策略可以保证最少需要的操作次数最小。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值