AtCoder Beginner Contest 452 题解
AtCoder Beginner Contest 452 题解
一般的场,套路题有点多。
Problem A. Gothec
- 定义下列日子为节日:
January 7
March 3
May 5
July 7
September 9
询问输入的日月是否算一其中的一个节日。
不解释。
#include <iostream>
#include <array>
#include <vector>
using std::array;
using std::cerr;
using std::cin;
using std::cout;
using std::vector;
const char endl = '\n';
const int kMaxN = 1e6 + 100;
void solve() {
int m, d;
cin >> m >> d;
if (m <= 9 && m == d && m != 1 && (m & 1)) {
return cout << "Yes" << endl, void();
} else if (m == 1 && d == 7) {
return cout << "Yes" << endl, void();
}
return cout << "No" << endl, void();
}
signed main() {
cin.tie(nullptr)->sync_with_stdio(false), cout.tie(nullptr), cerr.tie(nullptr);
int t = 1;
// cin >> t;
while (t--) solve();
return 0;
}
Problem B. Draw Frame
输入高度和宽度,打印一个类似下面的围栏:
#### #..# ####
不解释。
#include <iostream>
#include <array>
#include <vector>
using std::cerr;
using std::cin;
using std::cout;
using std::array;
using std::vector;
const char endl = '\n';
const int kMaxN = 1e6 + 100;
void solve() {
int h, w;
cin >> h >> w;
for (int i = 1; i <= h; i++) {
for (int j = 1; j <= w; j++) {
cout << ".#"[i == h || i == 1 || j == 1 || j == w];
}
cout << endl;
}
}
signed main() {
cin.tie(nullptr)->sync_with_stdio(false), cout.tie(nullptr), cerr.tie(nullptr);
int t = 1;
// cin >> t;
while (t--) solve();
return 0;
}
Problem C. Fishbones
给你 \(m\) 个长度不超过 \(10\) 的字符串,每个字符串可以视作一个骨头。如果一个骨头能够被当作脊椎,当且仅当:
- 长度为 \(n\),\(n \le 10\)
- 能够挑选 \(n\) 个字符串 \(s_i\)(可以重复选取),其中第 \(i\) 个字符串长度为 \(a_i\),第 \(b_i\) 个字符与脊椎的第 \(i\) 个字符一样
你需要输出每一根骨头是否可以当作脊椎。
模拟题:记录一个数组 \(has[i][j][ch]\),表示长度为 \(i\) 且 \(j\) 字符为 \(ch\) 的骨头是否存在。根据这个信息就可以判定了。
#include <iostream>
#include <array>
#include <vector>
using std::array;
using std::cerr;
using std::cin;
using std::cout;
using std::vector;
const char endl = '\n';
const int kMaxN = 1e6 + 100;
int n, m;
array<int, kMaxN> a, b;
array<std::string, kMaxN> str;
bool has[20][20][1145];
void solve() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i] >> b[i];
}
cin >> m;
for (int i = 1; i <= m; i++) {
cin >> str[i], str[i] = '#' + str[i];
for (int j = 1; j < str[i].size(); j++) {
has[str[i].size() - 1][j][str[i][j]] = true;
}
}
for (int i = 1; i <= m; i++) {
if (str[i].size() - 1 != n) goto bad;
for (int j = 1; j <= n; j++) {
if (has[a[j]][b[j]][str[i][j]] == false) goto bad;
}
if (1) {
cout << "Yes" << endl;
} else {
bad:
cout << "No" << endl;
}
}
}
signed main() {
cin.tie(nullptr)->sync_with_stdio(false), cout.tie(nullptr), cerr.tie(nullptr);
int t = 1;
// cin >> t;
while (t--) solve();
return 0;
}
Problem D. No-Subsequence Substring
给你两个字符串 \(S, T\),问 \(S\) 有多少个子串满足不存在子序列 \(T\)。
\(|S| \le 2\times 10^5, |T| \le 50\)
\(T\) 的范围很小,所以考虑:如果固定子串左边界 \(l\),那么当右边界拓展到哪里的时候才会一定出现 \(T\) 作为子序列。
这个其实有个自动机叫作子序列自动机/序列自动机。当然没有必要写这么重的东西。
记 \(nxt[i][ch]\) 数组为 \(i\) 出发第一个 \(ch\) 的位置。
于是你枚举 \(l\),然后把 \(T\) 丢进去跑一遍就出来了。
大概思想就是这样了,当然我代码实现的时候有些地方不一样。
#include <iostream>
#include <array>
#include <vector>
#define int long long
using std::array;
using std::cerr;
using std::cin;
using std::cout;
using std::vector;
const char endl = '\n';
const int kMaxN = 1e6 + 100;
std::string S, T;
int n;
array<int, 26> nxt[kMaxN];
void solve() {
cin >> S >> T, S = '#' + S;
n = S.size() - 1;
for (int j = 0; j < 26; j++) nxt[n + 2][j] = nxt[n + 1][j] = n + 2;
for (int i = n; i >= 1; i--) {
nxt[i] = nxt[i + 1];
nxt[i][S[i] - 'a'] = i + 1;
}
int ans = 0;
for (int i = 1, p; i <= n; i++) {
p = i;
for (auto ch : T) {
p = nxt[p][ch - 'a'];
}
ans += p - 1 - i;
}
cout << ans << endl;
}
signed main() {
cin.tie(nullptr)->sync_with_stdio(false), cout.tie(nullptr), cerr.tie(nullptr);
int t = 1;
// cin >> t;
while (t--) solve();
return 0;
}
Problem E. You WILL Like Sigma Problem
给你长度为\(n\)和\(m\) 的数组 \(a\) 和 \(b\)。计算
\[\sum_{i=1}^{n}\sum{j=1}^{m}a_i b_j (i\ mod\ j) \]
如果固定 \(i\),那么枚举 \(j\) 会发现其变化不够有规律。
但是如果固定 \(j\) 来枚举 \(i\),其数值一般类似 \(1 2 3 0 1 2 3 \cdots\) 这样的阶梯状。
同时一个容易知道的事情:\(\sum\) 交换顺序不影响求值,于是最后可以写成:
同时注意到 \((i\ mod\ j)\) 作为阶梯状贡献,其前缀和是好求的:维护 \(a_i\) 和 \(i\times a_i\) 前缀和然后简单运算就可以了。
至于复杂度:容易发现第二次的遍历为 \(\frac{n}{1} + \frac{n}{2} + \frac{n}{3}...\approx n\ln{n}\)。于是总复杂度大概就是 \(O(n\log n)\) 级别。
于是这个题就写完了。
#include <iostream>
#include <array>
#include <vector>
using std::cerr;
using std::cin;
using std::cout;
using std::array;
using std::vector;
const char endl = '\n';
const int kMaxN = 1e6 + 100;
const int MOD = 998244353;
int inc(int x, int y) {
return x + y >= MOD ? x + y - MOD : x + y;
}
int dec(int x, int y) {
return x - y < 0 ? x - y + MOD : x - y;
}
int mul(int x, int y) {
return 1ll * x * y % MOD;
}
int n, m;
array<int, kMaxN> a, b, s1, s2;
int qry(int l, int r) {
if (r > n) return qry(l, n);
return dec(dec(s2[r], s2[l - 1]), mul(l - 1, dec(s1[r], s1[l - 1])));
}
void solve() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
s1[i] = inc(s1[i - 1], a[i]), s2[i] = inc(s2[i - 1], mul(i, a[i]));
}
for (int i = 1; i <= m; i++) cin >> b[i];
int ans = 0;
// j = 1 的时候一定全是 0
for (int j = 2; j <= m; j++) {
int tmp = 0;
for (int i = 1; i <= n; i += j) {
tmp = inc(tmp, qry(i, i + j - 2));
}
ans = inc(ans, mul(b[j], tmp));
}
cout << ans << endl;
}
signed main() {
cin.tie(nullptr)->sync_with_stdio(false), cout.tie(nullptr), cerr.tie(nullptr);
int t = 1;
// cin >> t;
while (t--) solve();
return 0;
}
Problem F. Interval Inversion Count
给定一个排列 \(a\),问有多少个连续子序列满足逆序对个数刚好为 \(k\)。
容易发现对于固定的左端点,逆序对个数随着右端点单调不减。
但是注意到对于一个左端点 \(l\),其可能合法的右端点 \(r\) 有许多个。
所以我们需要做两次,第一次统计一个 \(R_1[i]\) 表示 \(i\) 开始第一个右端点使得 \([i, R[i]]\) 的逆序对个数大于 \(k\)。
第一次统计一个 \(R_2[i]\) 表示 \(i\) 开始第一个右端点使得 \([i, R[i]]\) 的逆序对个数大于等于 \(k\)。(也就是大于 \(k - 1\))
特别的,第二个情况可能要判一下 \(k=0\)。
#include <iostream>
#include <array>
#include <vector>
#define int long long
using std::array;
using std::cerr;
using std::cin;
using std::cout;
using std::vector;
const char endl = '\n';
const int kMaxN = 2e6 + 100;
int n, k;
array<int, kMaxN> val, a, r1, r2;
int lowbit(int x) {
return x & -x;
}
void add(int x, int v) {
for (; x < kMaxN; x += lowbit(x)) {
val[x] += v;
}
}
int qry(int x) {
int ans = 0;
for (; x; x -= lowbit(x)) {
ans += val[x];
}
return ans;
}
void solve() {
auto make = [](int x, array<int, kMaxN>& save) {
if (x < 0) {
for (int i = 1; i <= n; i++) save[i] = i;
return;
}
int l = 1, r = 0, now = 0;
val = {};
for (; l <= n; l++) {
while (r <= n && now <= x) {
r++;
if (r > n) break;
add(a[r], 1);
now += qry(n) - qry(a[r]);
}
save[l] = r;
add(a[l], -1);
now -= qry(a[l]);
}
};
cin >> n >> k;
for (int i = 1; i <= n; i++) cin >> a[i];
make(k - 1, r1), make(k, r2);
int ans = 0;
for (int i = 1; i <= n; i++) ans += r2[i] - r1[i];
cout << ans << endl;
}
signed main() {
cin.tie(nullptr)->sync_with_stdio(false), cout.tie(nullptr), cerr.tie(nullptr);
int t = 1;
// cin >> t;
while (t--) solve();
return 0;
}
Problem G. 221 Substring
给你一个由数字组成序列 \(a\),问有多少个本质不同的连续子序列 \(v\),满足:对于任意 \(v_i\),恰好有 \(v_i\) 个一模一样的字符连续。比如:
122333和223331。
本质不同子串就 SAM 没跑了
但是从逆序对计数到 SAM 是不是跨度有点大啊
我们可以先进行初步压缩,将连续的相同字符压缩到一起。
这样压缩一共会有三种情况:长度大于数字的,等于数字的,小于数字的。
容易发现等于数字的你爱怎么放怎么放,但是长度大于数字的只能出现在连续子序列的两个边界处并且通过截取获得刚好的部分。
而小于数字的无论如何你都无法使用。
这样你就会发现原字符串会被强行锁定若干起点和终点。
考虑使用广义 \(SAM\),这样就等价于从不同强制起点开始塞入字符串。
然后利用 \(SAM\) 计算本质不同子串就可以了。
#include <iostream>
#include <array>
#include <vector>
#define int long long
using std::array;
using std::cerr;
using std::cin;
using std::cout;
using std::vector;
const char endl = '\n';
const int kMaxN = 1e6 + 100;
struct sam {
struct node {
int link, len;
array<int, 10> nxt;
};
array<node, kMaxN> a;
int cnt = 0;
int newNode() {
a[++cnt] = {0, 0, {}};
return cnt;
}
int extend(int p, int ch) {
if (a[p].nxt[ch]) {
int q = a[p].nxt[ch];
if (a[p].len + 1 == a[q].len) {
return q;
} else {
int clone = newNode();
a[clone] = a[q], a[clone].len = a[p].len + 1;
a[q].link = clone;
while (~p && a[p].nxt[ch] == q) a[p].nxt[ch] = clone, p = a[p].link;
return clone;
}
}
int cur = newNode();
a[cur].len = a[p].len + 1;
while (~p && a[p].nxt[ch] == 0) a[p].nxt[ch] = cur, p = a[p].link;
if (p == -1) {
a[cur].link = 0;
} else {
int q = a[p].nxt[ch];
if (a[p].len + 1 == a[q].len) {
a[cur].link = q;
} else {
int clone = newNode();
a[clone] = a[q], a[clone].len = a[p].len + 1;
a[cur].link = a[q].link = clone;
while (~p && a[p].nxt[ch] == q) a[p].nxt[ch] = clone, p = a[p].link;
}
}
return cur;
}
int qry() {
int ans = 0;
for (int i = 1; i <= cnt; i++) ans += a[i].len - a[a[i].link].len;
return ans;
}
sam() {
cnt = 0, a[0].link = -1;
}
} sam;
int n;
array<int, kMaxN> a;
std::vector<int> v;
void solve() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1, j; i <= n; i = j) {
for (j = i; a[i] == a[j]; j++);
if (a[i] < j - i) {
v.push_back(-a[i]);
} else if (a[i] == j - i) {
v.push_back(a[i]);
} else {
v.push_back(0);
}
}
for (int i = 0; i < v.size();) {
if (v[i] == 0) {
i++;
continue;
}
int j = i, lst = 0;
for (; j < v.size(); j++) {
if (v[j] == 0) break;
lst = sam.extend(lst, std::abs(v[j]));
if (i != j && v[j] < 0) break;
}
i = j;
}
cout << sam.qry() << endl;
}
signed main() {
cin.tie(nullptr)->sync_with_stdio(false), cout.tie(nullptr), cerr.tie(nullptr);
int t = 1;
// cin >> t;
while (t--) solve();
return 0;
}

浙公网安备 33010602011771号