VP The 2024 ICPC Asia Hangzhou Regional Contest (The 3rd Universal Cup. Stage 25: Hangzhou)
A. AUS
题意:三个字符串,你要给每个字符定义一个值,使得\(s_1 = s_2\)且\(s_1 \ne s_3\)。
显然\(s_1, s_2\)对应位置字符的值是一样的,一个字符可能和多个字符值一样,那么可以把它们归为一类,用并查集维护,最后把\(s_1, s_3\)的字符变为对应的值,看两个字符串是不是相等。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
int fa[26];
int find(int x) {
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
void solve() {
std::string a, b, c;
std::cin >> a >> b >> c;
int n = a.size(), m = b.size(), k = c.size();
if (n != m) {
std::cout << "NO\n";
return;
}
if (n != k) {
std::cout << "YES\n";
return;
}
for (int i = 0; i < 26; ++ i) {
fa[i] = i;
}
for (int i = 0; i < n; ++ i) {
int x = a[i] - 'a', y = b[i] - 'a';
fa[find(y)] = find(x);
}
for (int i = 0; i < n; ++ i) {
a[i] = find(a[i] - 'a') + 'a';
c[i] = find(c[i] - 'a') + 'a';
}
if (a == c) {
std::cout << "NO\n";
} else {
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;
}
B. Barkley III
题意:一个数组恰好去掉一个数后剩下的数的按位与值中最大的是数组的值。现在给你一个数组,有区间操作给每个数与上一个数,和单点修改把一个数变成另一个数。然后询问是求\([l, r]\)这个子数组的值。
对于一个区间\([l, r]\),可以记录每一位有有多少数出现过,那么\(cnt_i = r - l\)说明这一位仅有一个数没有,那么去掉这个数就使得答案加\(2^i\)。显然一个从大到小找到最大的\(i\),然后把对应的这个数去掉。
那么可以线段树维护区间,记\(f_u, g_u\)分别为子区间的按位与和,和有哪些为只有一个数没出现过。那么有\(f_u = f_{ls} \& f_{rs}, g_u = (g_{ls} \& f_{rs}) | (f_{ls} \& g_{rs}\)。关于\(g\)的转移,因为\(g_{ls}\)代表是左儿子只有一个数没有出现的位,那么这些为还想成为当前区间的\(g\),则右儿子对应的位一定是都出现过,所以是与上右儿子的区间与。叶子节点要特殊处理一下。
那么查询时就可以得到这个区间的\(g\),找到最大的位,记为\(k\)。然后可以线段树二分找到这个数的位置,也就是查询大于等于\(l\)的第一个第\(k\)位是\(0\)的位置,因为这个数在\([l, r]\)区间里一定有,那么找到的大于等于\(l\)的第一个一定是在\([l, r]\)区间。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
using ui64 = unsigned long long;
#define ls (u << 1)
#define rs (u << 1 | 1)
constexpr int N = 1e6 + 5;
constexpr ui64 mask = (1ull << 63) - 1;
ui64 a[N];
struct Info {
ui64 f, g;
bool leaf = false;
void addTag(const ui64 & tag) {
if (leaf) {
f &= tag;
g = mask ^ f;
} else {
f &= tag;
g &= tag;
}
}
void set(const ui64 & x) {
f = x;
g = mask ^ x;
leaf = true;
}
};
Info operator + (const Info & a, const Info & b) {
Info res{};
res.f = a.f & b.f;
res.g = (a.g & b.f) | (a.f & b.g);
return res;
}
struct SegTree {
struct Node {
int l, r;
Info info;
ui64 tag = mask;
};
std::vector<Node> tr;
SegTree(int n) {
tr.assign(n << 2, {});
build(0, n - 1);
}
void pushup(int u) {
tr[u].info = tr[ls].info + tr[rs].info;
tr[u].tag = mask;
}
void pushdown(Node & u, const ui64 & tag) {
u.info.addTag(tag);
u.tag &= tag;
}
void pushdown(int u) {
if (tr[u].tag != mask) {
pushdown(tr[ls], tr[u].tag);
pushdown(tr[rs], tr[u].tag);
tr[u].tag = mask;
}
}
void build(int l, int r, int u = 1) {
tr[u] = {l, r, {}};
if (l == r) {
tr[u].info.set(a[l]);
tr[u].tag = mask;
return;
}
int mid = l + r >> 1;
build(l, mid, ls);
build(mid + 1, r, rs);
pushup(u);
}
void modify(int l, int r, const ui64 & 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, ls);
}
if (r > mid) {
modify(l, r, tag, rs);
}
pushup(u);
}
void modify(int p, const ui64 & x) {
int u = 1;
while (tr[u].l != tr[u].r) {
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if (p <= mid) {
u = ls;
} else {
u = rs;
}
}
tr[u].info.set(x);
u >>= 1;
while (u) {
pushup(u);
u >>= 1;
}
}
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, ls);
} else if (l > mid) {
return query(l, r, rs);
}
return query(l, r, ls) + query(l, r, rs);
}
int find(int p, int k, int u = 1) {
if (tr[u].info.f >> k & 1) {
return -1;
}
if (tr[u].l == tr[u].r) {
return tr[u].l;
}
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if (p <= tr[u].l) {
if (~tr[ls].info.f >> k & 1) {
return find(p, k, ls);
} else {
return find(p, k, rs);
}
} else if (p <= mid) {
int res = find(p, k, ls);
if (res == -1) {
return find(p, k, rs);
} else {
return res;
}
} else {
return find(p, k, rs);
}
}
};
void solve() {
int n, q;
std::cin >> n >> q;
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
SegTree tr(n);
while (q -- ) {
int op;
std::cin >> op;
if (op == 1) {
int l, r;
ui64 x;
std::cin >> l >> r >> x;
-- l, -- r;
tr.modify(l, r, x);
} else if (op == 2) {
int p;
ui64 x;
std::cin >> p >> x;
-- p;
tr.modify(p, x);
} else {
int l, r;
std::cin >> l >> r;
-- l, -- r;
auto [f, g, _] = tr.query(l, r);
ui64 ans = f;
for (i64 k = 62; k >= 0; -- k) {
if (g >> k & 1) {
int p = tr.find(l, k);
ans = mask;
if (p != l) {
ans &= tr.query(l, p - 1).f;
}
if (p != r) {
ans &= tr.query(p + 1, r).f;
}
break;
}
}
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. Elevator II
题意:起点为\(f\),每次增加\(1\)需要\(1\)代价,减少不需要代价。要完成\(n\)个二元组\((l_i, r_i)\),当你在\(l_i\)时加到\(r_i\)就完成了\(i\)。求完成所以二元组的最小代价。
首先每个二元组的代价无法避免,也就是至少有\(\sum_{i=1}^{n} r_i - l_i\)的代价。在这个基础上,我们希望增加的代价最小。
发现对于\(l_i \leq f\)的二元组,我们可以通过它们增加\(f\),也就是如果\(l_i \leq f\)且\(r_i > f\),我们可以不花费额外代价到达\(r_i\)。上升有什么好处呢?如果当前\(f\)大于等于所有\(l_i\),那么可以按\(l_i\)从大到小操作,因为下降是没有代价的,我们每次到\(l_i\)再到\(r_i\),此时依然大于等于\(l_{i-1}\),也就是不需要额外上升的代价。
那么每次二分找小于等于\(f\)的位置,求这个前缀最大的\(r\),一直往上跳,如果没到最上面就无法增加了,就花费额外代价跳到大于等于\(f\)最近的\(l_i\)。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n, f;
std::cin >> n >> f;
std::vector<std::array<int, 3>> a(n);
for (int i = 0; i < n; ++ i) {
int x, y;
std::cin >> x >> y;
a[i] = {x, y, i};
}
std::ranges::sort(a);
std::vector<int> l(n), r(n), id(n);
for (int i = 0; i < n; ++ i) {
l[i] = a[i][0];
r[i] = a[i][1];
id[i] = a[i][2];
}
std::vector<int> st(n);
std::vector<int> pre(n);
for (int i = 0; i < n; ++ i) {
pre[i] = i;
if (i) {
if (r[pre[i - 1]] > r[i]) {
pre[i] = pre[i - 1];
}
}
// std::cerr << l[i] << " " << r[i] << " " << id[i] << " " << pre[i] << "\n";
}
std::vector<int> ans;
ans.reserve(n);
i64 min = 0;
while (1) {
while (1) {
auto p = std::ranges::upper_bound(l, f) - l.begin();
-- p;
if (p == -1 || r[pre[p]] <= f) {
break;
}
st[pre[p]] = 1;
ans.push_back(id[pre[p]]);
f = r[pre[p]];
}
auto p = std::ranges::upper_bound(l, f) - l.begin();
if (p == n) {
break;
}
min += l[p] - f;
st[p] = 1;
ans.push_back(id[p]);
f = r[p];
}
for (int i = n - 1; i >= 0; -- i) {
min += r[i] - l[i];
if (!st[i]) {
ans.push_back(id[i]);
}
}
std::cout << min << "\n";
for (int i = 0; i < n; ++ i) {
std::cout << ans[i] + 1 << " \n"[i == n - 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;
}
F. Fuzzy Ranking
题意:\(n\)个人打了\(k\)场比赛,每场比赛由排名。如果一场比赛中\(x\)的排名大于\(y\)则\(x\)强于\(y\);或者如果\(x\)强于\(z\)而\(z\)强于\(y\)则\(x\)强于\(y\)。如果\(x, y\)都强于对方,则\((x, y)\)则个无序对是模糊的。每次求第\(k\)个比赛里排名\([l, r]\)的人里有多少对是模糊的。
容易发现是一个强连通分量的题。
那么问题变为第\(k\)个数组里\([l, r]\)有多少对数在同一个强连通分量里。记\(a[i][j]\)为第\(i\)个比赛第\(j\)个人,那么\(a[i][j]\)与\(a[i][j - 1]\)之间有边,那么如果\(a[i][j]\)和\(a[i][j - 1]\)不在一个强连通分量里,则\(a[i][j - 2]\)也不可能和\(a[i][j]\)在一个强连通分量里,也就是说可以把\(a[i]\)分成一些段,每一段的数都是同一个强连通分量的。
那么可以前缀和预处理每个数组每一段的答案。然后对于一个查询就是类似于分块的回答,先看\(l, r\)是不是在同一个段里,不是答案就是\(l\)所在这一段以\(l\)开头的后缀加\(l\)到\(r\)中间段的贡献加\(r\)这一段以\(r\)结尾的前缀。如果在同一个块就直接算。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n, k, q;
std::cin >> n >> k >> q;
std::vector<std::vector<int>> adj(n + 1);
std::vector a(k, std::vector<int>(n));
for (int i = 0; i < k; ++ i) {
for (int j = 0; j < n; ++ j) {
std::cin >> a[i][j];
if (j) {
adj[a[i][j]].push_back(a[i][j - 1]);
}
}
}
std::vector<int> dfn(n + 1), low(n + 1), stk(n + 1), in_stk(n + 1), bel(n + 1);
int idx = 0, cnt = 0;
stk.reserve(n);
auto tarjan = [&](auto & self, int u) -> void {
dfn[u] = low[u] = ++ idx;
stk.push_back(u);
in_stk[u] = 1;
for (auto & v : adj[u]) {
if (!dfn[v]) {
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]) {
++ cnt;
int v;
do {
v = stk.back();
stk.pop_back();
bel[v] = cnt;
in_stk[v] = 0;
} while (v != u);
}
};
for (int i = 1; i <= n; ++ i) {
if (!dfn[i]) {
tarjan(tarjan, i);
}
}
auto C = [&](int n) -> i64 {
return (i64)n * (n - 1) / 2;
};
std::vector<std::vector<i64>> sum(k), end(k);
std::vector id(k, std::vector<int>(n));
for (int t = 0; t < k; ++ t) {
sum[t].reserve(n + 1);
sum[t].push_back(0);
end[t].reserve(n + 1);
end[t].push_back(0);
for (int i = 0; i < n; ++ i) {
int j = i;
while (j + 1 < n && bel[a[t][j + 1]] == bel[a[t][i]]) {
++ j;
}
for (int l = i; l <= j; ++ l) {
id[t][l] = sum[t].size();
}
sum[t].push_back(sum[t].back() + C(j - i + 1));
end[t].push_back(j);
i = j;
}
}
i64 ans = 0;
while (q -- ) {
int t, l, r;
std::cin >> t >> l >> r;
t = (t + ans) % k;
l = (l + ans) % n;
r = (r + ans) % n;
if (id[t][l] == id[t][r]) {
ans = C(r - l + 1);
std::cout << ans << "\n";
} else {
ans = C(end[t][id[t][l]] - l + 1) + C(r - end[t][id[t][r] - 1]);
ans += sum[t][id[t][r] - 1] - sum[t][id[t][l]];
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;
}
H. Heavy-light Decomposition
题意:把一颗树的所有重链都给你。一个重链用\([l, r]\)表示,表示\(l, l + 1, l + 2, ..., r\)是一条重链。你要构造一棵树满足这些重链都是这棵树里的。
分类讨论。
把所有重链的长度记一下,那么如果\(max\)只有一个,可以把其它链都加到这条链的根上。否则需要加重一条最长链,需要把长度小于\(max\)的链加子树里,那么如果长度小于\(max\)的链长度都是\(max-1\)则无解,否则小于\(max-1\)都加到\(l+1\)上,其它链加到\(l\)上。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n, k;
std::cin >> n >> k;
std::vector<std::pair<int, int>> a(k);
for (int i = 0; i < k; ++ i) {
std::cin >> a[i].first >> a[i].second;
}
std::ranges::sort(a, [&](std::pair<int, int> & a, std::pair<int, int> & b) {
return a.second - a.first > b.second - b.first;
});
std::vector<std::vector<int>> g(n + 1);
for (int i = 1; i < k; ++ i) {
g[a[i].second - a[i].first + 1].push_back(i);
}
std::vector<int> fa(n + 1, 0);
int max = a[0].second - a[0].first + 1;
if (g[max].empty()) {
for (int i = max - 1; i >= 1; -- i) {
for (auto & j : g[i]) {
fa[a[j].first] = a[0].first;
}
}
} else {
int p = -1;
for (int i = 1; i < max - 1; ++ i) {
if (g[i].size()) {
p = i;
break;
}
}
if (p == -1) {
std::cout << "IMPOSSIBLE\n";
return;
}
for (int i = p; i < max - 1; ++ i) {
for (auto & j : g[i]) {
fa[a[j].first] = a[0].first + 1;
}
}
for (int i = max - 1; i <= max; ++ i) {
for (auto & j : g[i]) {
fa[a[j].first] = a[0].first;
}
}
}
for (int i = 0; i < k; ++ i) {
for (int j = a[i].first + 1; j <= a[i].second; ++ j) {
fa[j] = j - 1;
}
}
for (int i = 1; i <= n; ++ i) {
std::cout << fa[i] << " \n"[i == 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;
}
K. Kind of Bingo
题意:一个\(n\times m\)的排列放在\(n\times m\)的矩阵里,从上到下从左到右每次把第\(a[i][j]\)个位置标记。你有\(k\)次操作可以交换两个数。求一行都被标记的最小时间。
二分时间。\(check\)里就是枚举行,看这行都被标记需要交换几个数。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n, m, k;
std::cin >> n >> m >> k;
std::vector a(n, std::vector<int>(m));
for (int i = 0; i < n; ++ i) {
for (int j = 0; j < m; ++ j) {
std::cin >> a[i][j];
}
}
std::vector<int> s(n * m + 1);
auto check = [&](int x) -> bool {
std::ranges::fill(s, 0);
for (int i = 0, c = 0; i < n && c < x; ++ i) {
for (int j = 0; j < m && c < x; ++ j, ++ c) {
s[a[i][j]] = 1;
}
}
for (int i = 1; i <= n * m; ++ i) {
s[i] += s[i - 1];
}
for (int i = m; i <= n * m; i += m) {
int cnt = s[i] - s[i - m];
if (m - cnt <= k) {
return true;
}
}
return false;
};
int lo = m, hi = n * m;
while (lo < hi) {
int mid = lo + hi >> 1;
if (check(mid)) {
hi = mid;
} else {
lo = mid + 1;
}
}
std::cout << lo << "\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;
}
M. Make It Divisible
题意:一个数组,你可以给每个数同时加\([1, k]\)之间的一个数,使得每个子区间都有一个数整除其它数。求有多少个数以及它们的和。
如果数组数都相同都\([1, k]\)都可以选。
然后观察性质,发现这个数一定是区间最小值,然后加上\(x\)后这个数得是区间\(gcd\),那么我们知道\(gcd(a, b) = gcd(b, a - b)\)。也就是可以枚举区间里相邻两个数的差,取它们的\(gcd\)。那么\(min + x\)一定整数这个\(gcd\)。那么可以枚举\(gcd\)的因子,得到\(min\)需要加多少,然后检查一些是否合法。
不过每个区间的\(min\)都不一样,可以用单调栈求出笛卡尔树,然后枚举整个区间的\(gcd\),也就是根节点满足条件的数,然后每个点的父节点只需要加上\(x\)后整除整个点就行了,因为父节点的父节点需要整除父节点,那么传递下来每个节点整除它的所有子节点,也就是\(x\)对于每个区间都满足条件。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n, k;
std::cin >> n >> k;
std::vector<int> a(n + 2);
int min = 2e9;
for (int i = 1; i <= n; ++ i) {
std::cin >> a[i];
min = std::min(min, a[i]);
}
if (std::ranges::count(a, a[1]) == n) {
std::cout << k << " " << (i64)k * (k + 1) / 2 << "\n";
return;
}
a[0] = -2e9;
a[n + 1] = -1e9;
std::vector<int> stk;
stk.push_back(0);
std::vector<int> l(n + 2), r(n + 2), fa(n + 2);
for (int i = 1; i <= n + 1; ++ i) {
int u = -1;
while (a[stk.back()] >= a[i]) {
r[stk.back()] = i - 1;
u = stk.back();
stk.pop_back();
}
if (u != -1) {
fa[u] = i;
}
l[i] = stk.back() + 1;
fa[i] = stk.back();
stk.push_back(i);
}
auto b = a;
std::sort(b.begin() + 1, b.begin() + n + 1);
int d = 0;
for (int i = 1; i + 1 <= n; ++ i) {
d = std::gcd(d, b[i + 1] - b[i]);
}
int cnt = 0;
i64 sum = 0;
auto work = [&](int d) -> void {
if (d <= min) {
return;
}
int x = d - min;
if (x > k) {
return;
}
for (int i = 1; i <= n; ++ i) {
if (a[i] != min) {
if ((a[i] + x) % (a[fa[i]] + x)) {
return;
}
}
}
++ cnt;
sum += x;
};
for (int i = 1; i <= d / i; ++ i) {
if (d % i == 0) {
work(i);
if (i * i != d) {
work(d / i);
}
}
}
std::cout << cnt << " " << sum << "\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;
}

浙公网安备 33010602011771号