The 2024 ICPC Asia Hangzhou Regional Contest
外榜 https://board.xcpcio.com/icpc/49th/hangzhou
铜线: 4题739 ~ 5题726
银线: 5题723-370 ~ 6题552
金线: 6题495-470 7题+
难度(个人感觉):
纯签: AK
半签: H
Easy: EM
Mid:
Hard: BF 主要是本蒟蒻不会SCC(bushi
别的难以触碰
A
题意
三个字符串\(s_1 s_2 s_3\),重新映射26个小写字母,问能不能存在映射方案使得\(s_1=s_2\)且\(s_1 \neq s_3\)。
思路
先根据长度特判一下,如果全等长,再把\(s_2\)每个字母全变成\(s_1\)的,直接判断\(s_1s_3\)是不是相等就好了。
代码
cpp
struct DSU {
std::vector<int> fa, siz;
DSU(int n) : fa(n + 1), siz(n + 1, 1) {
std::iota(all(fa), 0);
}
int find(int x) {
while (x != fa[x]) {
x = fa[x] = fa[fa[x]];
}
return x;
}
bool merge(int x, int y) {
x = find(x); y = find(y);
if (x == y) return 0;
siz[x] += siz[y];
fa[y] = x;
return 1;
}
};
void solve() {
string a, b, c;
cin >> a >> b >> c;
int x = a.size(), y = b.size(), z = c.size();
if (x != y) {
cout << "NO\n";
return;
}
if (x != z) {
cout << "YES\n";
return;
}
DSU dsu(200);
for (int i = 0; i < x; i++) {
dsu.merge(a[i], b[i]);
}
for (int i = 0; i < x; i++) {
if (dsu.find(a[i]) != dsu.find(c[i])) {
cout << "YES\n";
return;
}
}
cout << "NO\n";
}
K
题意
一个\(n \times m\)排列\(p\),一个\(n \times m\)网格 第\(i\)行\(j\)列编号\((i-1)\times m+j\)。有最多\(k\)次修改排列的机会,每次可以交换两个元素。
第\(x\)次操作标记\(p_x\)位置,求最小的\(x\),使得\(x\)次操作后存在某一行全部被标记。
思路
先把排列按行分开,考虑每一行,求出该行被完全标记所需的最小操作次数。从后往前枚举,要想减少答案,最后一个肯定要往前挪,答案就是下标第\(k+1\)大,取答案最小的那一行和\(m\)的较大值。
代码
cpp
void solve() {
int n, m, k;
cin >> n >> m >> k;
vector<int> p(n * m);
for (int i = 0; i < n * m; i++) {
cin >> p[i];
}
int ans = n * m;
std::vector<int> cnt(n);
for (int i = n * m - 1; i >= 0; i--) {
int t = (p[i] - 1) / m;
cnt[t]++;
if (cnt[t] <= k + 1) {
ans = std::min(ans, i + 1);
}
}
cout << std::max(ans, m) << "\n";
}
H
题意
一棵有根树,将每个非根节点标记成轻或重。
要求每个非叶子节点的子节点中,子树大小最大的是重,别的都是轻。这样每个节点都属于一条重链(\(x_1, x_2,...x_k\),满足\(x_1\)是轻或者根,\(x_i\)是\(x_{i-1}\)的子节点且为重,\(x_k\)是叶子)。
输入\(k\)条重链,构造树的结构,输出每个节点的父亲,或者说不存在方案。
思路
普遍想法,把所有链都挂到最长链的头,这样最长链的头当根,判断合不合法就行,只要最长唯一,就合法,不唯一就把最小的挂上去变成最长。
特判一下特殊情况,比如一条链,最长不唯一且挂上最小也不合法,所有链等长。
代码
cpp
void solve() {
int n, k;
cin >> n >> k;
struct node {
int l, r, len;
};
vector<node> a(k);
for (int i = 0; i < k; i++) {
auto &[l, r, len] = a[i];
cin >> l >> r;
len = r - l + 1;
}
std::sort(all(a), [&] (auto x, auto y) {
return x.len > y.len;
});
if (k == 1) {
for (int i = 0; i < n; i++) {
cout << i << " \n"[i == n - 1];
}
return;
}
if (a[0].len == a.back().len) {
cout << "IMPOSSIBLE\n";
return;
}
vector<int> fa(n + 1);
for (int i = 0; i < k; i++) {
if (i) {
fa[a[i].l] = a[0].l;
}
for (int j = a[i].l + 1; j <= a[i].r; j++) {
fa[j] = j - 1;
}
}
if (a[0].len == a[1].len) {
if (a[0].len - 2 < a.back().len) {
cout << "IMPOSSIBLE\n";
return;
}
fa[a.back().l] = a[0].l + 1;
}
for (int i = 1; i <= n; i++) {
cout << fa[i] << " \n"[i == n];
}
}
E
题意
大楼里一座电梯,最初在第\(F\)层,每次最多只能载一人。有\(n\)个人,第\(i\)个人想从第\(l_i\)层乘坐到第\(r_i\)层,把\(n\)个人运送完,求电梯的最小上升层数和载人顺序。
思路
- 究极贪心
答案肯定不小于所有人的区间之和,再加上无用功(不载人往上走)。
当前所在位置在某个人的区间之内,去载他,没有就直接往上走,直到最高点。这时再往下,遇到r就载。
本着能载就载,不能就走的原则,到达最高点,此后不会再有无用功。
因此,按照\(r\)排序,先找到达最高点的序列,剩下的依次即可。
代码
cpp
void solve() {
int n, F;
cin >> n >> F;
struct node {
int l, r, id;
};
vector<node> a(n);
ll ans = 0;
for (int i = 0; i < n; i++) {
auto &[l, r, id] = a[i];
cin >> l >> r;
id = i + 1;
if (r >= F) {
F = std::min(F, l);
}
ans += r - l;
}
std::sort(all(a), [&] (auto a, auto b) {
return a.r == b.r ? a.l < b.l : a.r > b.r;
});
std::deque<int> q;
vector<bool> vis(n + 1);
int now = a[0].r;
int add = 2e9;
for (int i = 0; i < n; i++) {
if (a[i].l >= F) {
add = std::min(add, a[i].l - F);
if (now > a[i].r) {
ans += now - a[i].r;
}
if (a[i].l < now) {
q.push_front(a[i].id);
vis[a[i].id] = 1;
}
now = std::min(now, a[i].l);
}
}
for (int i = 0; i < n; i++) {
if (!vis[a[i].id]) {
q.push_back(a[i].id);
}
}
if (add != 2e9) ans += add;
cout << ans << '\n';
while (!q.empty()) {
cout << q.front() << " ";
q.pop_front();
}
cout << '\n';
}
M
题意
一个序列\(b\)和一个整数\(k \le 10^9\),求\(\sum_{x=1}^{k} [b_1+x,b_2+x,...b_n+x为可整除序列] \times x\)。
可整除区间: 存在整数\(d \in [l, r]\)满足\(b_l,b_{l+1},...b_r\)内的所有数都能被\(b_d\)整除。
可整除序列: 对于任意区间,都是可整除区间。
思路
首先要知道,原数组gcd,可以整除差分数组gcd,也一定是最小值的因子,即\(gcd_原\)=std::gcd(min,\(gcd_{差分}\))。
区间越长,gcd的限制一定越多,也就越小。对于大区间[l,r]满足的\(x\),小区间[a,b]也满足。
所以先预处理出整个区间满足条件的\(x\)(即差分gcd的因子减去min并且\(1 \le x-min \le k\))存下来,答案一定是这个集合的子集。
然后去枚举\(i\),判断以\(i\)位置为最小值的最大区间(单调栈)符不符合。
应该就是个笛卡尔树。本蒟蒻不会
代码
cpp
constexpr int N = 5e4 + 5;
constexpr int M = 18;
int Log[N], power[M];
int init = []() {
Log[2] = 1;
for (int i = 3; i < N; i++) {
Log[i] = Log[i >> 1] + 1;
}
power[0] = 1;
for (int i = 1; i < M; i++) {
power[i] = power[i - 1] << 1;
}
return 0;
}();
template<typename Info>
struct SparseTable {
Info info[M][N];
void assign(int n) {
for (int i = 0; i < M; i++) {
for (int j = 0; j <= n; j++) {
info[i][j] = Info();
}
}
}
void build(auto &a) {
int n = a.size() - 1;
assign(n);
for (int i = 0; i <= n; i++) {
info[0][i] = Info(a[i]);
}
for (int i = 1; i < M; i++) {
for (int j = 0; j + power[i] - 1 <= n; j++) {
info[i][j] = info[i - 1][j] + info[i - 1][j + power[i - 1]];
}
}
}
Info query(int l, int r) {
int log = Log[r - l + 1];
return info[log][l] + info[log][r - power[log] + 1];
}
};
struct Info {
int gcd;
Info(int gcd = 0) : gcd(gcd) {}
};
Info operator + (const Info &l, const Info &r) {
Info res;
res.gcd = std::gcd(l.gcd, r.gcd);
return res;
}
SparseTable<Info> ST;
void solve() {
int n, k;
cin >> n >> k;
vector<int> b(n + 1);
for (int i = 1; i <= n; i++) {
cin >> b[i];
}
int min = *std::min_element(b.begin() + 1, b.end());
int max = *std::max_element(all(b));
if (max == min) {
cout << k << " " << 1LL * (1 + k) * k / 2 << "\n";
return;
}
vector<int> diff(n + 1);
for (int i = 1; i <= n; i++) {
diff[i] = std::abs(b[i] - b[i - 1]);
}
ST.build(diff);
vector<int> x;
int g0 = ST.query(2, n).gcd;
for (int i = 1; i * i <= g0; i++) {
if (g0 % i == 0) {
if (i > min && i - min <= k) {
x.push_back(i - min);
}
if (i * i < g0 && g0 / i > min && g0 / i - min <= k) {
x.push_back(g0 / i - min);
}
}
}
std::vector<int> stk(n + 1);
std::vector<int> l(n + 1), r(n + 1, n + 1);
int top = 0;
for (int i = 1; i <= n; i++) {
while (top && b[stk[top]] >= b[i]) {
int j = stk[top--];
r[j] = i;
if (top) {
l[j] = stk[top];
}
}
stk[++top] = i;
}
while (top) {
int j = stk[top--];
if (top) {
l[j] = stk[top];
}
}
for (int i = n; i >= 1; i--) {
if (r[i] <= n && b[i] == b[r[i]]) {
r[i] = r[r[i]];
}
}
vector<bool> del((int)x.size());
for (int i = 1; i <= n; i++) {
int L = l[i] + 1, R = r[i] - 1;
if (L >= R) continue;
int g = ST.query(L + 1, R).gcd;
for (int j = 0; j < x.size(); j++) {
if (std::gcd(b[i] + x[j], g) % (b[i] + x[j])) {
del[j] = 1;
}
}
}
ll ans = 0, cnt = 0;
for (int i = 0; i < x.size(); i++) {
if (!del[i]) {
ans += x[i];
cnt++;
}
}
cout << cnt << " " << ans << '\n';
}
B
题意
子序列\(a_{p_1},a_{p_2},...a_{p_k}\)的等级为\(k\)个数的按位与。
\(q\)次查询,
1 l r x: 区间内每个数按位与上x, \(a_l,a_{l+1},...a_r\)变成\(a_l\)&x, \(a_{l+1}\)&x,...\(a_r\)&x。
2 s x: \(a_s\) = x
3 l r (l < r): 求区间内选r-l个数组成的序列的最大等级。
思路
- 线段树二分
修改操作线段树懒标记修改即可。
对于查询,区间内删除只有一个0别的都是1的最高数位所在的数。枚举每个数位,二分删除的位置,时间复杂度\(O(q\times logn \times loga_i)\)不能接受。
看官方题解,难点就在于状态压缩,用一个数表示当前区间内某个数位恰好一个0。
代码
cpp
template<class Info, class Tag>
class LazySegmentTree {
private:
int n;
std::vector<Info> info;
std::vector<Tag> tag;
void apply(int o, const Tag &t) { tag[o].apply(t), info[o].apply(t); }
void push(int o) { apply(o << 1, tag[o]), apply(o << 1 | 1, tag[o]), tag[o] = Tag(); }
void pull(int o) { info[o] = info[o << 1] + info[o << 1 | 1]; }
public:
LazySegmentTree(int n) : n(n), info(n << 2, Info()), tag(n << 2, Tag()) {}
void build(int o, int l, int r, const std::vector<Info> &a) {
if (l == r) {
info[o] = a[l];
return;
}
int mid = l + r >> 1;
build(o << 1, l, mid, a);
build(o << 1 | 1, mid + 1, r, a);
pull(o);
}
void build(const std::vector<Info> &a) { build(1, 1, n, a); }
void modify(int o, int l, int r, int x, int y, const Tag &tag) {
if (x <= l && r <= y) {
apply(o, tag);
return;
}
push(o);
int mid = l + r >> 1;
if (x <= mid) modify(o << 1, l, mid, x, y, tag);
if (y > mid) modify(o << 1 | 1, mid + 1, r, x, y, tag);
pull(o);
}
void modify(int o, int l, int r, int p, const Tag &tag) {
if (l == r) {
info[o] = Info(tag.mask, (~tag.mask), 1);
return;
}
push(o);
int mid = l + r >> 1;
if (p <= mid) modify(o << 1, l, mid, p, tag);
else modify(o << 1 | 1, mid + 1, r, p, tag);
pull(o);
}
void modify(int p, const Tag &tag) { modify(1, 1, n, p, tag); }
void modify(int l, int r, const Tag &tag) { modify(1, 1, n, l, r, tag); }
Info query(int o, int l, int r, int x, int y) {
if (x <= l && r <= y) {
return info[o];
}
push(o);
int mid = l + r >> 1;
if (y <= mid) return query(o << 1, l, mid, x, y);
if (x > mid) return query(o << 1 | 1, mid + 1, r, x, y);
return query(o << 1, l, mid, x, y) + query(o << 1 | 1, mid + 1, r, x, y);
}
Info query(int p) { return query(1, 1, n, p, p); }
Info query(int l, int r) { return query(1, 1, n, l, r); }
int get(int o, int l, int r, int p, int k) {
if ((info[o].sum >> k & 1)) {
return 0;
}
if (l == r) {
return l;
}
push(o);
int mid = l + r >> 1;
if (p > mid) {
return get(o << 1 | 1, mid + 1, r, p, k);
}
int L = get(o << 1, l, mid, p, k);
return L ? L : get(o << 1 | 1, mid + 1, r, p, k);
}
int get(int p, int k) { return get(1, 1, n, p, k); }
};
struct Tag {
bool need;
ll mask;
Tag(bool need = 0, ll mask = ~0LL) : need(need), mask(mask) {}
void apply(const Tag &t) {
if (t.need) {
mask &= t.mask;
need = 1;
}
}
};
struct Info {
long long sum, flag;
int len;
Info(long long sum = ~0LL, long long flag = 0, int len = 1) : sum(sum), flag(flag), len(len) {}
void apply(const Tag &t) {
if (t.need) {
sum &= t.mask;
if (len == 1) {
flag = (flag & t.mask) | (~t.mask);
} else {
flag &= t.mask;
}
}
}
};
Info operator+(const Info &L, const Info &R) {
Info res;
res.sum = L.sum & R.sum;
res.flag = (L.flag & R.sum) | (R.flag & L.sum);
res.len = L.len + R.len;
return res;
}
void solve() {
int n, q;
cin >> n >> q;
vector<ll> a(n + 1);
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
vector<Info> aa(n + 1);
for (int i = 1; i <= n; i++) {
aa[i] = Info(a[i], (~a[i]), 1);
}
LazySegmentTree<Info, Tag> seg(n);
seg.build(aa);
while (q--) {
int op;
cin >> op;
if (op == 1) {
int l, r;
ll x;
cin >> l >> r >> x;
seg.modify(l, r, Tag(1, x));
} else if (op == 2) {
int s;
ll x;
cin >> s >> x;
seg.modify(s, Tag(0, x));
} else {
int l, r;
cin >> l >> r;
if (l == r) {
cout << 0 << "\n";
continue;
}
auto [res, flag, len] = seg.query(l, r);
if (!flag) {
cout << res << "\n";
continue;
}
for (int i = 63; i >= 0; i--) {
if (flag >> i & 1) {
int p = seg.get(l, i);
ll ans = (~0LL);
if (p > l) {
ans &= seg.query(l, p - 1).sum;
}
if (p < r) {
ans &= seg.query(p + 1, r).sum;
}
cout << ans << '\n';
break;
}
}
}
}
}