2024 ICPC杭州

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;
                }
            }
        }
    }
}