AtCoder Beginner Contest 268

E - Chinese Restaurant (Three-Star Version)

假设旋转 \(x\) 次时, \(n\) 个人失望值的总和为 \(c_x\),那么只要能求出 \(c_x, 0 \le x < n\) 就可以包含所有情况,然后再取最小值即可。

对于第 \(0\) 个人,假设 \(p_0 = 0\),第 \(0\) 个人对 \(c_x\) 的贡献依次为 \(0, 1, 2, \dots, y - 1, y, y - 1, \dots, 1\);假设 \(p_0 = x\),那么贡献旋转左移 \(x\) 次即可得到对应的贡献。

类似的,可以算出每个人对答案的贡献,但是朴素的算复杂度爆炸,需要优化。

对于第 \(i\) 个人的贡献,可以视为区间加一个 \(1, 2, \dots, y - 1, y, y - 1, \dots, 1\) ,这种操作其实很经典,二阶差分后就只剩常数个非零值了,先只维护二阶差分数组就可以 \(O(1)\) 完成操作,操作完再恢复回来,就能得到原数组。

AC代码
// Problem: E - Chinese Restaurant (Three-Star Version)
// Contest: AtCoder - UNIQUE VISION Programming Contest 2022 Summer (AtCoder Beginner Contest 268)
// URL: https://atcoder.jp/contests/abc268/tasks/abc268_e
// Memory Limit: 1024 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>

#define CPPIO std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);

#ifdef BACKLIGHT
#include "debug.h"
#else
#define logd(...) ;
#define ASSERT(x) ;
#define serialize() std::string("")
#endif

using i64 = int64_t;
using u64 = uint64_t;

void Initialize();
void SolveCase(int Case);

int main(int argc, char* argv[]) {
  CPPIO;
  int T = 1;
  // std::cin >> T;
  for (int t = 1; t <= T; ++t) {
    SolveCase(t);
  }
  return 0;
}

void Initialize() {}

void SolveCase(int Case) {
  int n;
  std::cin >> n;

  std::vector<int> p(n);
  for (int i = 0; i < n; ++i) {
    std::cin >> p[i];
  }

  std::vector<i64> d2(2 * n + 1, 0);

  auto add = [&](int d) {
    int x = 0, y = n / 2, z = n / 2 + n % 2, w = n;

    logd(x, y, z, w);

    ++d2[x + d + 1];
    --d2[y + d + 1];
    --d2[z + d + 1];
    ++d2[w + d + 1];

    // std::vector<i64> d1(2 * n + 1);
    // for (int i = 1; i <= 2 * n; ++i)
    // d1[i] = d1[i - 1] + d2[i];
    // logd(d1);
    //
    // std::vector<i64> c(2 * n + 1);
    // for (int i = 1; i <= 2 * n; ++i)
    // c[i] = c[i - 1] + d1[i];
    // logd(c);
  };

  for (int i = 0; i < n; ++i) {
    int d = i <= p[i] ? p[i] - i : n + (p[i] - i);
    add(d);
  }

  std::vector<i64> d1(2 * n + 1);
  for (int i = 1; i <= 2 * n; ++i)
    d1[i] = d1[i - 1] + d2[i];
  logd(d1);

  std::vector<i64> c(2 * n + 1);
  for (int i = 1; i <= 2 * n; ++i)
    c[i] = c[i - 1] + d1[i];
  logd(c);

  for (int i = 1; i <= n; ++i)
    c[i] = c[i] + c[i + n];
  logd(c);

  std::cout << *std::min_element(c.begin() + 1, c.begin() + n + 1) << "\n";
}

F - Best Concatenation

首先,将分数分成每个 \(s_i\) 内部的,以及不同 \(s_i\) 之间的。对于前者,不管怎么排列都不会影响,可以直接后缀和算。

假设当前的排列为 \(T_1T_2\dots T_{|T|}\) ,假设 \(T_i\) 包含 \(cx_i\)X\(T_i\) 内的数位之和为 \(cd_i\) ,则此时的分数为

\[score = \sum_{i = 1}^{n} cx_i \sum_{j = i + 1}^{n} cd_j \]

交换 \(T_i\)\(T_{i+1}\) ,记 \(D = \sum_{j = i + 2}^{n}cd_j\) ,则分数的变化量为

\[\Delta = cx_{i+1}(cd_i + D) + cx_i D - \left[ cx_i(cd_{i+1} + D) + cx_{i+1}D \right] \]

如果交换后的方案更优,则 \(\Delta > 0\) ,即\(cx_{i+1}cd_i > cx_i cd_{i+1}\)

那么根据这个条件去将 \(T_i\) 排序即可得到最优的方案。

AC代码
// Problem: F - Best Concatenation
// Contest: AtCoder - UNIQUE VISION Programming Contest 2022 Summer (AtCoder Beginner Contest 268)
// URL: https://atcoder.jp/contests/abc268/tasks/abc268_f
// Memory Limit: 1024 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>

#define CPPIO std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);

#ifdef BACKLIGHT
#include "debug.h"
#else
#define logd(...) ;
#define ASSERT(x) ;
#define serialize() std::string("")
#endif

using i64 = int64_t;
using u64 = uint64_t;

void Initialize();
void SolveCase(int Case);

int main(int argc, char* argv[]) {
  CPPIO;
  int T = 1;
  // std::cin >> T;
  for (int t = 1; t <= T; ++t) {
    SolveCase(t);
  }
  return 0;
}

void Initialize() {}

void SolveCase(int Case) {
  int n;
  std::cin >> n;

  i64 ans = 0;
  std::vector<std::pair<int, int>> c(n);

  for (int _ = 0; _ < n; ++_) {
    std::string s;

    std::cin >> s;

    int cx = 0, cd = 0;
    for (int i = s.size() - 1; i >= 0; --i) {
      if (s[i] == 'X') {
        ++cx;
        ans += cd;
      } else {
        cd += s[i] - '0';
      }
    }
    c[_] = {cx, cd};
  }

  std::sort(c.begin(), c.end(),
            [](const std::pair<int, int>& lhs, const std::pair<int, int>& rhs) -> bool {
              return i64(1) * lhs.first * rhs.second > i64(1) * rhs.first * lhs.second;
            });

  i64 s = 0;
  for (int i = n - 1; i >= 0; --i) {
    ans += s * c[i].first;
    s += c[i].second;
  }

  std::cout << ans << "\n";
}

G - Random Student ID

对于字符串 \(s_i\) ,其排名等于小于等于它的字符串的数量之和。记 \(P_{i, j}\) 表示 \(s_j\) 小于等于 \(s_i\) 的概率,则 \(s_i\) 排名的期望等于 \(\sum_j P_{i, j}\)

对于两个字符串 \(s_i\)\(s_j\) ,分类讨论:

  • 假设 \(s_j\)\(s_i\) 的前缀(包含 \(s_j = s_i\) )的情况,则 \(s_j\) 永远小于等于 \(s_i\),即 \(p_{i, j} = 1\)
  • 假设 \(s_i\)\(s_j\) 的前缀(不包含 \(s_j = s_i\) )的情况,则 \(s_j\) 永远大于 \(s_i\),即 \(p_{i, j} = 0\)
  • 剩余情况的话,就是 \(s_i\)\(s_j\)\(k - 1\) 个字符相同,第 \(k\) 个字符不同,那么个第 \(k\) 个字符的大小就决定了两个串的大小。然后根据对称性可得 \(p_{i, j} = \frac{1}{2}\)

由此,假设有 \(a_i\)\(j\) 满足第一种情况, \(b_i\)\(j\) 满足第二种情况,则有 \(N - a_i - b_i\)\(j\) 满足第三种情况,\(\sum_j P_{i, j} = \frac{a_i - b_i + N}{2}\)

然后 \(a_i\)\(b_i\) 可以通过 Trie 计算。

然后完事了。

AC代码
// Problem: G - Random Student ID
// Contest: AtCoder - UNIQUE VISION Programming Contest 2022 Summer (AtCoder Beginner Contest 268)
// URL: https://atcoder.jp/contests/abc268/tasks/abc268_g
// Memory Limit: 1024 MB
// Time Limit: 3000 ms
//
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>

#define CPPIO std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);

#ifdef BACKLIGHT
#include "debug.h"
#else
#define logd(...) ;
#define ASSERT(x) ;
#define serialize() std::string("")
#endif

using i64 = int64_t;
using u64 = uint64_t;

void Initialize();
void SolveCase(int Case);

int main(int argc, char* argv[]) {
  CPPIO;
  int T = 1;
  // std::cin >> T;
  for (int t = 1; t <= T; ++t) {
    SolveCase(t);
  }
  return 0;
}

void Initialize() {}

template <typename ValueType, ValueType mod_, typename SupperType>
class Modular {
  static ValueType normalize(ValueType value) {
    if (value >= 0 && value < mod_)
      return value;
    value %= mod_;
    if (value < 0)
      value += mod_;
    return value;
  }

  static ValueType power(ValueType value, int64_t exponent) {
    ValueType result = 1;
    ValueType base = value;
    while (exponent) {
      if (exponent & 1)
        result = SupperType(result) * base % mod_;
      base = SupperType(base) * base % mod_;
      exponent >>= 1;
    }
    return result;
  }

 public:
  Modular(ValueType value = 0) : value_(normalize(value)) {}

  Modular(SupperType value) : value_(normalize(value % mod_)) {}

  ValueType value() const { return value_; }

  Modular inv() const { return Modular(power(value_, mod_ - 2)); }

  Modular power(int64_t exponent) const { return Modular(power(value_, exponent)); }

  friend Modular operator+(const Modular& lhs, const Modular& rhs) {
    ValueType result = lhs.value() + rhs.value() >= mod_ ? lhs.value() + rhs.value() - mod_
                                                         : lhs.value() + rhs.value();
    return Modular(result);
  }

  friend Modular operator-(const Modular& lhs, const Modular& rhs) {
    ValueType result = lhs.value() - rhs.value() < 0 ? lhs.value() - rhs.value() + mod_
                                                     : lhs.value() - rhs.value();
    return Modular(result);
  }

  friend Modular operator-(const Modular& lhs) {
    ValueType result = normalize(-lhs.value() + mod_);
    return result;
  }

  friend Modular operator*(const Modular& lhs, const Modular& rhs) {
    ValueType result = SupperType(1) * lhs.value() * rhs.value() % mod_;
    return Modular(result);
  }

  friend Modular operator/(const Modular& lhs, const Modular& rhs) {
    ValueType result = SupperType(1) * lhs.value() * rhs.inv().value() % mod_;
    return Modular(result);
  }

  std::string to_string() const { return std::to_string(value_); }

 private:
  ValueType value_;
};

// using Mint = Modular<int, 1'000'000'007, int64_t>;
using Mint = Modular<int, 998'244'353, int64_t>;

class Binom {
 private:
  std::vector<Mint> f, g;

 public:
  Binom(int n) {
    f.resize(n + 1);
    g.resize(n + 1);

    f[0] = Mint(1);
    for (int i = 1; i <= n; ++i)
      f[i] = f[i - 1] * Mint(i);
    g[n] = f[n].inv();
    for (int i = n - 1; i >= 0; --i)
      g[i] = g[i + 1] * Mint(i + 1);
  }
  Mint operator()(int n, int m) {
    if (n < 0 || m < 0 || m > n)
      return Mint(0);
    return f[n] * g[m] * g[n - m];
  }
};

struct Trie {
  Trie* child_[26];

  int id_;
  int e_;
  int up_, down_;

  Trie() {
    e_ = 0;
    for (int i = 0; i < 26; ++i)
      child_[i] = nullptr;
  }

  ~Trie() {
    for (int i = 0; i < 26; ++i) {
      if (child_[i]) {
        delete child_[i];
        child_[i] = nullptr;
      }
    }
  }

  void Insert(const std::string& s, int id) {
    int n = s.size();
    Trie* p = this;

    for (int i = 0; i < n; ++i) {
      int c = s[i] - 'a';

      if (p->child_[c] == nullptr) {
        p->child_[c] = new Trie();
      }

      p = p->child_[c];
    }

    ++p->e_;
    p->id_ = id;
  }

  void Work() {
    std::function<void(Trie*, int)> dfs = [&](Trie* p, int up) {
      p->up_ = up + p->e_;
      p->down_ = 0;

      for (int i = 0; i < 26; ++i) {
        if (p->child_[i]) {
          dfs(p->child_[i], p->up_);
          p->down_ += p->child_[i]->down_ + p->child_[i]->e_;
        }
      }
    };
    dfs(this, 0);
  }

  std::vector<Mint> Solve(int n) {
    std::vector<Mint> r(n);

    std::function<void(Trie*)> dfs = [&](Trie* p) {
      if (p->e_) {
        r[p->id_] = Mint(p->up_ - p->down_ + n) / Mint(2);
        logd(p->id_, p->up_, p->down_, Mint(p->up_ - p->down_ + n));
      }

      for (int i = 0; i < 26; ++i) {
        if (p->child_[i]) {
          dfs(p->child_[i]);
        }
      }
    };
    dfs(this);

    return r;
  }
};

void SolveCase(int Case) {
  int n;
  std::cin >> n;

  Trie trie;
  for (int i = 0; i < n; ++i) {
    std::string s;
    std::cin >> s;

    trie.Insert(s, i);
  }

  trie.Work();
  auto a = trie.Solve(n);

  for (auto x : a)
    std::cout << x.value() << "\n";
}

Ex - Taboo

建出 AC 自动机,然后拿 \(s\) 在自动机上跑,如果跑到一个包含某个 \(T_i\)的状态,就把最后遍历到的\(s\)的字符改成 *,相当于重新从 AC 自动机的根开始跑。

AC代码
// Problem: Ex - Taboo
// Contest: AtCoder - UNIQUE VISION Programming Contest 2022 Summer (AtCoder Beginner Contest 268)
// URL: https://atcoder.jp/contests/abc268/tasks/abc268_h
// Memory Limit: 1024 MB
// Time Limit: 4000 ms
//
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>

#define CPPIO std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);

#ifdef BACKLIGHT
#include "debug.h"
#else
#define logd(...) ;
#define ASSERT(x) ;
#define serialize() std::string("")
#endif

using i64 = int64_t;
using u64 = uint64_t;

void Initialize();
void SolveCase(int Case);

int main(int argc, char* argv[]) {
  CPPIO;
  int T = 1;
  // std::cin >> T;
  for (int t = 1; t <= T; ++t) {
    SolveCase(t);
  }
  return 0;
}

void Initialize() {}

template <int alpha = 256, typename String = std::string>
struct AcAutomaton {
 public:
  struct Node {
   public:
    Node()
        : child_(alpha, nullptr),
          trans_(alpha, nullptr),
          fail_(nullptr),
          pass_(0),
          end_(0),
          mark_(0) {}

    ~Node() {
      for (int i = 0; i < alpha; ++i) {
        if (child_[i]) {
          delete child_[i];
          child_[i] = nullptr;
        }
      }
    }

   public:
    std::vector<Node*> child_;
    std::vector<Node*> trans_;
    Node* fail_;
    int pass_;
    int end_;

   public:
    int mark_;
  };

 public:
  void InsertTrie(const String& s) {
    Node* p = root_;
    for (int i = 0; i < s.size(); ++i) {
      int c = s[i];
      if (p->child_[c] == nullptr) {
        p->child_[c] = new Node();
      }
      p = p->child_[c];
      ++p->pass_;
    }
    ++p->end_;
  }

  void BuildFail() {
    std::queue<Node*> q;
    q.push(root_);

    while (!q.empty()) {
      Node* p = q.front();
      q.pop();

      for (int c = 0; c < alpha; ++c) {
        if (p->child_[c]) {
          p->trans_[c] = p->child_[c];

          if (p == root_)
            p->trans_[c]->fail_ = root_;
          else
            p->trans_[c]->fail_ = p->fail_->trans_[c];

          q.push(p->child_[c]);
        } else {
          if (p == root_)
            p->trans_[c] = root_;
          else
            p->trans_[c] = p->fail_->trans_[c];
        }
      }
    }
  }

  void Work() {
    std::queue<Node*> q;
    q.push(root_);

    while (!q.empty()) {
      Node* p = q.front();
      q.pop();

      if (p != root_)
        p->mark_ = (p->end_ > 0) | p->fail_->mark_;

      for (int c = 0; c < alpha; ++c) {
        if (p->child_[c]) {
          q.push(p->child_[c]);
        }
      }
    }
  }

 public:
  AcAutomaton() : root_(new Node()) {}

  ~AcAutomaton() { delete root_; }

 public:
  Node* root_;
};
using AcAM = AcAutomaton<26, std::vector<int>>;

void SolveCase(int Case) {
  std::string s;
  std::cin >> s;

  AcAM ac;
  int n;
  std::cin >> n;
  std::string t;
  for (int i = 0; i < n; ++i) {
    std::cin >> t;

    std::vector<int> v(t.size());
    for (int i = 0; i < t.size(); ++i)
      v[i] = t[i] - 'a';
    ac.InsertTrie(v);
  }
  ac.BuildFail();
  ac.Work();

  AcAM::Node* p = ac.root_;
  int ans = 0;
  for (int i = 0; i < s.size(); ++i) {
    int c = s[i] - 'a';
    p = p->trans_[c];
    if (p->mark_) {
      p = ac.root_;
      ++ans;
    }
  }

  std::cout << ans << "\n";
}

posted @ 2022-09-18 12:03  _Backl1ght  阅读(200)  评论(0编辑  收藏  举报