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\) ,则此时的分数为
交换 \(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";
}