2025牛客暑期多校训练营10
D. Grammar Test (grammar)
找规律题。取模是吓人的。发现只有\(01010..\)或者\(101010..\)两种,然后发现长度为\(4\)加上偶数个\(3\)。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
i64 n;
std::cin >> n;
if (n >= 4 && (n - 4) % 3 == 0 && (n - 4) / 3 % 2 == 0) {
std::cout << 2 << "\n";
} else {
std::cout << 0 << "\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. Sensei and Affection (affection)
题意:一个序列,每次你可以选择一个区间使得它们加一。求最多出现\(m\)种不同数的最小操作数。\(n\leq 400, m \in {1, 2}, a_i \leq 100\)。
\(m\)很小,显然可以分类讨论。
如果\(m=1\),也就是所有数都要变成一样的,肯定是变成最大值,那么每个数要加\(x - a_i\)次,这是个经典的差分问题,考虑记\(a_0 = a_{n+1} = 0\);\(d_i = a_i - a_{i-1}, i \in [1, n + 1]\)。那么一次区间操作就是在\(d\)里选一个正数一个负数然后正数减一负数加一。最终\(d\)种所有数都变成\(0\)就说明所有数都相等了。其操作数为\(\sum_{i=1}^{n+1} \max(d_i, 0) = \sum_{i=1}^{n+1} \max(a_i - a_{i-1}, 0)\)。发现\(d\)中负数和正数总是能抵消的,也可以写为\(\frac{\sum_{i=1}^{n+1} |a_i - a_{i-1}|}{2}\)。
如果\(m=2\),考虑算\(\sum_{i=1}^{n+1} |a_i - a_{i-1}|\),最后结果除\(2\)就行。这个情况可以变成两个数,那么可以枚举这两个数\(x, y\),记\(f[i][0/1]\)表示\(i\)变成\(x\)或者\(y\)的前缀\(|a_i - a_{i-1}|\)的最小和。那么就容易转移了,只需要枚举\(a_i\)变成\(x\)还是变成\(y\)和\(i-1\)是\(x\)还是\(y\)。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n, m;
std::cin >> n >> m;
std::vector<int> a(n + 2);
for (int i = 1; i <= n; ++ i) {
std::cin >> a[i];
}
if (m == 1) {
int max = std::ranges::max(a);
for (int i = 1; i <= n; ++ i) {
a[i] = max - a[i];
}
int ans = 0;
for (int i = 1; i <= n + 1; ++ i) {
ans += std::max(0, a[i] - a[i - 1]);
}
std::cout << ans << "\n";
} else {
int max = std::ranges::max(a);
int min = std::ranges::min(a);
const int inf = 1e9;
int ans = inf;
for (int x = min; x <= 200; ++ x) {
for (int y = std::max(max, x); y <= 200; ++ y) {
std::array<int, 2> f{inf, inf};
if (a[1] <= x) {
f[0] = x - a[1];
}
if (a[1] <= y) {
f[1] = y - a[1];
}
for (int i = 2; i <= n; ++ i) {
std::array<int, 2> g{inf, inf};
if (a[i] <= x) {
g[0] = std::min(f[1] + std::abs(y - a[i - 1] - (x - a[i])) , f[0] + std::abs(x - a[i - 1] - (x - a[i])));
}
if (a[i] <= y) {
g[1] = std::min({f[1] + std::abs(y - a[i - 1] - (y - a[i])), f[0] + std::abs(x - a[i - 1] - (y - a[i]))});
}
f = g;
}
ans = std::min({ans, f[0] + std::abs(x - a[n]), f[1] + std::abs(y - a[n])});
}
}
std::cout << ans / 2 << "\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. Sensei and Yuuka Going Shopping (yuuka)
题意:一个数组要求分成三个连续的部分,使得三个部分拥有的相同的数的种类最多。
考虑枚举第一部分的右端点,然后找最优的第二部分的右端点。
记\(last_x\)为\(x\)在\(a\)中最后出现的位置,\(next_i\)为\(i\)下一个和\(a_i\)相等的位置。
那么如果枚举到\(i\)为第一部分的右端点,则这个数想要产生贡献,第二部分右端点要在\([next_i, last_{a_i} - 1]\)这个区间。如果我们转化为区间加,那么就变成了求区间最大值。
可以用线段树维护,注意一些细节即可。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
template <class Info, class Tag>
struct LazySegmentTree {
struct Node {
int l, r;
Info info;
Tag tag;
};
std::vector<Node> tr;
LazySegmentTree() {}
LazySegmentTree(int n) {
init(n);
}
void init(int n) {
tr.assign(n << 2, {});
build(0, n - 1);
}
void pushdown(Node & u, const Tag & tag) {
u.info = u.info + tag;
u.tag = u.tag + tag;
}
void pushdown(int u) {
if (tr[u].tag.exist()) {
pushdown(tr[u << 1], tr[u].tag);
pushdown(tr[u << 1 | 1], tr[u].tag);
tr[u].tag.clear();
}
}
void pushup(int u) {
tr[u].info = tr[u << 1].info + tr[u << 1 | 1].info;
}
void build(int l, int r, int u = 1) {
tr[u] = {l, r, {}};
if (l == r) {
tr[u].info.p = l;
return;
}
int mid = l + r >> 1;
build(l, mid, u << 1); build(mid + 1, r, u << 1 | 1);
pushup(u);
}
void modify(int l, int r, const Tag & tag, int u = 1) {
if (l <= tr[u].l && tr[u].r <= r) {
pushdown(tr[u], tag);
return;
}
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if (l <= mid) {
modify(l, r, tag, u << 1);
}
if (r > mid) {
modify(l, r, tag, u << 1 | 1);
}
pushup(u);
}
Info query(int l, int r, int u = 1) {
if (l <= tr[u].l && tr[u].r <= r) {
return tr[u].info;
}
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if (r <= mid) {
return query(l, r, u << 1);
} else if (l > mid) {
return query(l, r, u << 1 | 1);
}
return query(l, r, u << 1) + query(l, r, u << 1 | 1);
}
};
struct Info {
int max, p;
};
struct Tag {
int add;
bool exist() {
return add != 0;
}
void clear() {
add = 0;
}
};
Info operator + (const Info & a, const Info & b) {
return a.max >= b.max ? a : b;
}
Info operator + (const Info & a, const Tag & b) {
Info res{};
res.max = a.max + b.add;
res.p = a.p;
return res;
}
Tag operator + (const Tag & a, const Tag & b) {
Tag res{};
res.add = a.add + b.add;
return res;
}
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
std::vector<int> next(n, -1);
std::map<int, int> mp, last;
for (int i = n - 1; i >= 0; -- i) {
if (mp.count(a[i])) {
next[i] = mp[a[i]];
} else {
last[a[i]] = i;
}
mp[a[i]] = i;
}
int ans = 0, ansl = 1, ansr = n - 1;
LazySegmentTree<Info, Tag> tr(n);
std::map<int, int> pre;
for (int i = 0; i + 1 < n; ++ i) {
int x = a[i];
if (next[i] == -1) {
} else if (last[x] == next[i]) {
if (pre.count(x)) {
tr.modify(i, last[x] - 1, Tag{-1});
}
} else {
if (!pre.count(x)) {
tr.modify(next[i], last[x] - 1, Tag{1});
} else {
tr.modify(i, last[x] - 1, Tag{-1});
tr.modify(next[i], last[x] - 1, Tag{1});
}
}
pre[a[i]] = i;
auto [max, p] = tr.query(i + 1, n - 1);
if (max > ans) {
ans = max;
ansl = i + 1;
ansr = p + 1;
}
}
std::cout << ans << "\n";
std::cout << ansl + 1 << " " << ansr + 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;
}
H. Rev Equation (NOI-tAUqe ver.) (equation)
可以看出两个运算的数相同那么最短的回文肯定是前面添等于,所以无解。
除了这个情况,还有除了\(0-x=\)这种减法有解外,其它都无解。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
std::string s;
std::cin >> s;
if (s[0] == s[2]) {
std::cout << "No\n";
return;
}
if (s[1] == '-' && s[0] != '0') {
std::cout << "No\n";
return;
}
std::cout << "Yes\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;
}
I. Matrix (matrix)
题意:构造题。有一个\(n\times m\)的循环矩阵。你要把\([1, n\times m]\)的数一次填上去,使得\(i\)从上下左右某个方向走\(i\)步可以到\(i+1\)。
玩了半天才看出来,只能说构造题都有点找规律的感觉。
把\(1\)填在\((1, 1)\),然后按左右左右的方向交错着填,每填满一行后需要换行,就上下上下交错着来。然后\(lcm(n, m) < nm\)无解。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
const int dx[] = {1, 0, -1, 0}, dy[] = {0, 1, 0, -1};
void solve() {
int n, m;
std::cin >> n >> m;
if (std::lcm(n, m) < n * m) {
std::cout << "NO\n";
return;
}
std::vector a(n, std::vector<int>(m));
for (int k = 1, x = 0, y = 0, dx = 0, dy = 0; k <= n * m; ++ k) {
a[x][y] = k;
if (k % m == 0) {
if (dx == 0) {
x = ((x - k) % n + n) % n;
} else {
x = (x + k) % n;
}
dx ^= 1;
} else {
if (dy == 0) {
y = ((y - k) % m + m) % m;
} else {
y = (y + k) % m;
}
dy ^= 1;
}
}
std::cout << "YES\n";
for (int i = 0; i < n; ++ i) {
for (int j = 0; j < m; ++ j) {
std::cout << a[i][j] << " \n"[j == m - 1];
}
}
}
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;
}
K. Amazing Sets (amazing)
题意:一棵有向树,根可以到所有点。每个点有权值,现在加了一些边\((u, v)\),那么\(v\)是\(u\)的祖先。然后选出一些点集来,使得每个点可以到底的点都在这个点集内,求所有合法点集有多少不同的和。
如果不加边,问题就是选若干个子树,那么可以树上背包。
现在加了边,可以考虑缩点,发现缩点后依然是一棵有向数,且根可以到任意点。那么树上背包就行。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
bool f[10001][10001];
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
std::vector<std::vector<int>> adj(n);
std::vector<int> in(n);
for (int i = 1; i < n; ++ i) {
int u, v;
std::cin >> u >> v;
-- u, -- v;
adj[u].push_back(v);
++ in[v];
}
int root = 0;
while (in[root]) {
++ root;
}
int m;
std::cin >> m;
for (int i = 0; i < m; ++ i) {
int u, v;
std::cin >> u >> v;
-- u, -- v;
adj[u].push_back(v);
}
std::vector<int> dfn(n, -1), low(n), id(n, -1), stk(n + 10), in_stk(n), w(n);
int top = 0, idx = 0, cnt = 0;
auto tarjan = [&](auto & self, int u) -> void {
dfn[u] = low[u] = idx ++ ;
stk[ ++ top] = u; in_stk[u] = 1;
for (auto & v : adj[u]) {
if (dfn[v] == -1) {
self(self, v);
low[u] = std::min(low[u], low[v]);
} else if (in_stk[v]) {
low[u] = std::min(low[u], dfn[v]);
}
}
if (low[u] == dfn[u]) {
int v;
do {
v = stk[top -- ];
in_stk[v] = 0;
id[v] = cnt;
w[cnt] += a[v];
} while (v != u);
++ cnt;
}
};
tarjan(tarjan, root);
std::vector<std::vector<int>> adj1(cnt);
for (int u = 0; u < n; ++ u) {
for (auto & v : adj[u]) {
if (id[u] != id[v]) {
adj1[id[u]].push_back(id[v]);
}
}
}
std::vector<int> sum(cnt);
auto dfs = [&](auto & self, int u) -> void {
f[u][0] = true;
std::ranges::sort(adj1[u]);
adj1[u].erase(std::unique(adj1[u].begin(), adj1[u].end()), adj1[u].end());
for (auto & v : adj1[u]) {
self(self, v);
for (int i = sum[u]; i >= 0; -- i) {
if (f[u][i]) {
for (int j = sum[v]; j > 0; -- j) {
f[u][i + j] |= f[v][j];
}
}
}
sum[u] += sum[v];
}
sum[u] += w[u];
f[u][sum[u]] = true;
};
root = id[root];
dfs(dfs, root);
int ans = 0;
for (int i = 0; i <= sum[root]; ++ i) {
ans += f[root][i];
}
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;
}