牛客周赛 Round 103
A. 清楚
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n;
std::cin >> n;
if (n % 10) {
std::cout << "YES\n";
} else {
std::cout << "NO\n";
}
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
// std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
B. 姐姐
题意:给定一个环形数组,你可以选定一个起点,这样就破环成链了,求有没有办法使得这个数组不递减。
把数组复制一份到后面,然后从前往后记录有多少个数是不递减的,当个数到\(n\)时说明以\(i-n\)作为起点可以使得数组不递减。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
a.insert(a.end(), a.begin(), a.end());
for (int i = 1, cnt = 1; i < 2 * n; ++ i) {
if (a[i] >= a[i - 1]) {
++ cnt;
} else {
cnt = 1;
}
if (cnt == n) {
std::cout << "YES\n";
return;
}
}
std::cout << "NO\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
C. 智乃
题意:一个长度为\(n\)的排列为好排列当且仅当该排列满足恰好存在\(n\)个子区间是一个排列。求长度为\(n\)的排列有多少个好排列。
因为要有\(n\)个子区间是排列,那么\(n\)要么在第一个要么在最后一个。则\(cnt_n = cnt_{n-1} \times 2\)。因为长度为\(1\)的好排列只有一个,可得\(cnt_n = 2^{n-1}\)。
代码省略取模类。
点击查看代码
constexpr int P = 1000000007;
using Z = MInt<P>;
void solve() {
int n;
std::cin >> n;
std::cout << power<Z>(2, n - 1) << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
// std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
D. 哥哥
题意:给你一个\(01\)串,你可以任意修改其中的字符。求\(01\)子串的个数加\(10\)子串的个数至少是\(3\)个最小修改数。
和上周牛客周赛一个题差不多,区间只在这个是至少\(3\)个,另一个是恰好\(3\)个。
那么一样的\(dp\),设\(f[i][j][k]\)为前\(i\)个有\(k\)个子串且第\(i\)个位置是\(j\)的最小修改数。我们转移时发现如果子串个数大于等于\(3\),就和\(3\)取\(min\)就行了。这样\(f[i][j][3]\)就表示大于等于\(3\)的答案。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n;
std::cin >> n;
std::string s;
std::cin >> s;
const int inf = 1e9;
std::array<std::array<int, 2>, 4> f;
for (int i = 0; i < 4; ++ i) {
f[i].fill(inf);
}
for (int i = 0; i < 2; ++ i) {
f[0][i] = s[0] - '0' != i;
}
for (int i = 1; i < n; ++ i) {
std::array<std::array<int, 2>, 4> g{};
for (int j = 0; j < 4; ++ j) {
g[j].fill(inf);
}
for (int j = 0; j < 4; ++ j) {
for (int x = 0; x < 2; ++ x) {
for (int y = 0; y < 2; ++ y) {
int t = x != y;
int nj = std::min(3, j + t);
g[nj][y] = std::min(g[nj][y], f[j][x] + (y != s[i] - '0'));
}
}
}
f = g;
}
int ans = std::min(f[3][0], f[3][1]);
std::cout << ans << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
E. 新婚
题意:一棵树,每个点有点权,点权为\(0\)或\(1\)。一条路径的值为起点到终点构成的\(01\)串的二进制值。多次询问,有没有至少一条路径的值是\(x\),满足这个路径的一个端点是另一个端点的祖先。
\(x \leq 2^{20}\)。意味着路径长度小于等于\(20\)。那么每个点暴力往上爬就行,长度大于等于\(20\)就退出,然后每一步得到的二进制串正着一遍反着一遍算出值存到\(set\)里。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n, q;
std::cin >> n >> q;
std::string s;
std::cin >> s;
std::vector<std::vector<int>> adj(n);
for (int i = 1; i < n; ++ i) {
int u, v;
std::cin >> u >> v;
-- u, -- v;
adj[u].push_back(v);
adj[v].push_back(u);
}
std::vector<int> fa(n, -1);
std::set<int> st;
auto get = [&](std::string & t) -> int {
int res = 0;
for (auto & c : t) {
res = res * 2 + c - '0';
}
return res;
};
auto getr = [&](std::string t) -> int {
std::ranges::reverse(t);
int res = 0;
for (auto & c : t) {
res = res * 2 + c - '0';
}
return res;
};
auto dfs = [&](auto & self, int u) -> void {
int x = 0, y = u;
int cnt = 0;
std::string t;
while (cnt <= 20 && y != -1) {
t += s[y];
st.insert(get(t));
st.insert(getr(t));
y = fa[y];
++ cnt;
}
for (auto & v : adj[u]) {
if (v == fa[u]) {
continue;
}
fa[v] = u;
self(self, v);
}
};
dfs(dfs, 0);
while (q -- ) {
int x;
std::cin >> x;
if (st.count(x)) {
std::cout << "YES\n";
} else {
std::cout << "NO\n";
}
}
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
// std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
F. 快乐
题意:给定一个字符串\(s\)和\(m\)个字符串,你可以选择\(s\)的一个后缀,使得这个后缀和所有\(m\)个字符串的最长公共前缀的长度和最大。
我们把\(m\)个字符串插到\(trie\)里,并且记录一个\(sum_u\)表示根到\(u\)这个节点被所有字符串经过的长度和。那么给定一个后缀,答案就是这个后缀在\(trie\)里代表的节点\(u\)的\(sum_u\)。但如果每个后缀都查一遍依然是\(O(n^2)\)的,可以用\(AC\)自动机建出来\(fail\)树,那么给定一个字符串\(s\),一直跳\(fail\)链,那么如果一个后缀在\(m\)字符串里其中一个的前缀里出现过,就可以找到对应的节点。但可能最佳的后缀,只是有一部分前缀出现在\(m\)个字符串里,那么这需要删除\(s\)的一些后缀再进行查询才能查的到。
发现我们大可不必跳\(fail\)链,直接在构造\(fail\)树时记录\(fail\)链上最大的\(sum_u\)。可以做到\(O(1)\)查询,那么时间复杂度就只在枚举删除后缀上,那么可以存\(trie\)里每个节点父亲的编号,就可以一步一步往上爬,时间复杂度\(O(n)\),\(AC\)自动机的时间复杂度\(O(nlogn)\)。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
constexpr int SIZE = 26;
constexpr int N = 6e5 + 5;
int tr[N][SIZE], fa[N], len[N], next[N];
i64 cnt[N], sum[N], max[N];
int idx;
struct ACAM {
ACAM() {
init();
}
int newNode() {
memset(tr[idx], 0, sizeof tr[idx]);
cnt[idx] = 0;
next[idx] = 0;
len[idx] = 0;
sum[idx] = 0;
fa[idx] = 0;
return idx ++ ;
}
void init() {
idx = 0;
newNode();
}
int insert(const std::string & s, int add = 1) {
int p = 0;
for (auto & c : s) {
int x = c - 'a';
if (!tr[p][x]) {
tr[p][x] = newNode();
len[tr[p][x]] = len[p] + 1;
fa[tr[p][x]] = p;
}
p = tr[p][x];
cnt[p] += add;
}
return p;
}
void build() {
std::queue<int> q;
for (int i = 0; i < SIZE; ++ i) {
if (tr[0][i]) {
q.push(tr[0][i]);
sum[tr[0][i]] = cnt[tr[0][i]];
}
}
while (q.size()) {
int u = q.front(); q.pop();
for (int i = 0; i < SIZE; ++ i) {
int v = tr[u][i];
if (!v) {
tr[u][i] = tr[next[u]][i];
} else {
next[v] = tr[next[u]][i];
sum[v] = cnt[v] + sum[u];
q.push(v);
}
}
max[u] = std::max(sum[u], max[next[u]]);
}
}
};
void solve() {
int n, m;
std::cin >> n >> m;
std::string s;
std::cin >> s;
ACAM ac;
for (int i = 0; i < m; ++ i) {
std::string t;
std::cin >> t;
ac.insert(t);
}
int p = ac.insert(s, 0);
ac.build();
i64 ans = 0;
while (p) {
ans = std::max(ans, max[p]);
p = fa[p];
}
std::cout << ans << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
写在最后的话:祝清楚姐姐智乃哥哥新婚快乐!