cf1046 div3 + 2024 ICPC Hongkong

cf1046 div3 and 2024 ICPC Hongkong

div3

E

因为每个数只能被操作一次,所有被操作之后必须要是目标值。于是对于一个数,如果它当前不和目标值符合,就要尝试异或一个后面一个数,而且后面一个数也只有两种情况:目标值和原来的值。

所以直接特判一下最后一个数然后遍历一遍就好了。鉴定为中杯。

    bool ok = true;
    if (a[n] != b[n]) {
        ok = false;
    }
    for (int i = 1; i < n && ok; ++i) {
        if ((a[i] ^ b[i + 1]) != b[i] && (a[i] ^ a[i + 1]) != b[i] && a[i] != b[i]) {
            ok = false;
        }
    }

F

实际上若有一条从 \((1, 1)\)\((x, y)\) 路径且路径上的值都为 0,则 \(a\) 的前 \(x\) 个数都相等且等于 \(b\) 的前 \(y\) 个数。具体是 0 或者是 1 就决定了需要操作多少次。

对两个序列分别维护出将前 \(i\) 个数修改成 1 / 0 的最小操作次数是容易的:

std::vector<std::array<int, 3>> work(const std::string &s) {
    std::vector f(n + 1, std::array<int, 3>{ 0, 0, 0 });
    for (int i = 1; i <= n; ++i) {
        int d = s[i] - '0';
        f[i][d] = f[i - 1][d];
        f[i][d ^ 1] = f[i - 1][d ^ 1] + 1;
    }
    return f;
}

假设对于 \(a\) 我们用以上函数算出了 \(p\),对于 \(b\) 我们用以上函数算出了 \(q\)。我们基于此考虑如何算价值,假设我们到 \((x, y)\) 是设为 0 更优,则必然满足:

\[p[x][0] + q[y][0] \leq p[x][1] + q[y][1] \]

移项可得:

\[p[x][0] - p[x][1] \leq q[y][1] - q[y][0] \]

于是我们对 \(p\) 每一项按照 \(p[x][0] - p[x][1]\) 升序排序,对 \(q\) 的每一项按照 \(q[y][1] - q[y][0]\) 升序排序,同时算一下 \(q\) 的前后缀。遍历 \(x\) 二分一下取 0 和取 1 的分界点就好了。

auto p = work(a);
    for (int i = 1; i <= n; ++i) {
        p[i][2] = p[i][0] - p[i][1];
    }
    auto q = work(b);
    for (int i = 1; i <= n; ++i) {
        q[i][2] = q[i][1] - q[i][0];
    }
 
    std::sort(p.begin() + 1, p.end(), cmp);
    std::sort(q.begin() + 1, q.end(), cmp);
    std::vector pre(n + 1, 0ll), suf(n + 2, 0ll);
    for (int i = 1; i <= n; ++i) {
        pre[i] = pre[i - 1] + q[i][1];
    }
    for (int i = n; i >= 1; --i) {
        suf[i] = suf[i + 1] + q[i][0];
    }
 
    i64 ans = 0;
    for (int i = 1; i <= n; ++i) {
        int idx = std::upper_bound(q.begin() + 1, q.end(), p[i], cmp) - q.begin();
        ans += 1ll * (idx - 1) * p[i][1] + pre[idx - 1] + 1ll * (n - idx + 1) * p[i][0] + suf[idx];
    }

鉴定为排序题

G

这题观察不难,主要是要时间复杂度正确的实现。

    // 预处理部分
    val[0] = 1;
    pre[0] = 1ll;
    for (int i = 1; i <= N; ++i) {
        val[i] = (i * pre[i - 1]) % Mod;
        pre[i] = (pre[i - 1] * val[i]) % Mod;
    }

    // 计算部分
    for (auto &ele : s) {
        num = ele;
 
        if (num <= 31 && ope(num) <= k) {
            k -= ope(num);
            (ans *= val[num]) %= Mod;
        }
        else {
            break;
        }
    }
    while (k > 0) {
        int MSB = std::__lg(k);
        k -= (1 << MSB);
        
        (ans *= num) %= Mod;
        (ans *= pre[MSB]) %= Mod;
        num = MSB + 1;
    }
    std::cout << ans << '\n';

ICPC Hongkong

做之前:什么叫去年香港2铜3题银5题金

做之后:太过困难

K

可以逆着考虑,即对于 \(t_i\) 可以在 R 右边添加任意字符,在 L 左边添加任意字符,看能否凑成 \(s\)

深入的去想会发现 \(t_i\) 中的组合 LR 必须放在一起,但实际上我们并不用特意去考虑这个问题,因为当你确定了 L 的位置后,你在这个位置后找的 R 的位置可能并不与之前 L 的位置相邻,但是我们一定可以把 L 的位置向后移知道和 R 相邻。大概就是当 s 是 LLLLLR \(t_i\) 是 LR 时,我们去找 L 的位置会找到第一个位置,我们去找 R 的位置会找到最后一个位置,此时虽然不相邻,但是一定可以将 L 移动到与 R 相邻的位置。所以只用考虑一点特殊情况:\(t_i\) 开头是 R 时,由于不能往左边添加任何字符,所以 \(s\) 的开头也必须时 R,结尾同理考虑。

L

看题解写出来的构造……

此题最重要的观察就是,对于任意一条题目中描述的路径,一定会经过所有的反对角线(即“ / ”这样的)一次且仅一次。如果考虑每条反对角线上与目标颜色相异(枚举目标颜色)的颜色数量,结合上面的观察就能得到这些数量必须有相同的奇偶性。

当所有的反对角线都具有相同的颜色,我们就一定可以构造出合法的路径组合。具体的,我们考虑依次满足所有主对角线(实现上我是从右向左依次满足每条主对角线的),对于每条主对角线(除了左下角那个),我们每次先走到下一条主对角线的最上面一个点,然后看右边的点是否是目标颜色,是就先下后右,否则先右后下将它变成目标颜色,直到不能再下了就一直往右走到终点。

bool find(std::vector<std::vector<bool>> g) {
    ans.clear();

    std::vector anti_cnt(n + m, 0);
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < m; ++j) {
            if (g[i][j]) {
                anti_cnt[i + j] += 1;
            }
        }
    }
    for (int i = 1; i <= n + m - 2; ++i) {
        if ((anti_cnt[i] - anti_cnt[i - 1]) % 2 != 0) {
            return false;
        }
    }

    std::string path;
    int x, y;
    auto down = [&]() -> void {
        x += 1;
        g[x][y] = !g[x][y];
        path.push_back('D');
    };
    auto right = [&]() -> void {
        y += 1;
        g[x][y] = !g[x][y];
        path.push_back('R');
    };
    for (int k = 1 - m; k + 1 < n; ++k) {
        x = 0, y = 0;
        int tx = 0, ty = 0;
        path.clear();
        if (k < 0) {
            tx = 0, ty = -k - 1;
        }
        else {
            tx = k + 1, ty = 0;
        }

        g[0][0] = !g[0][0];
        for (int i = 0; i < tx; ++i) {
            down();
        }
        for (int i = 0; i < ty; ++i) {
            right();
        }

        while (x < n - 1 && y < m - 1) {
            if (g[x][y + 1]) {
                right();
                down();
            }
            else {
                down();
                right();
            }
        }
        while (x + 1 < n) {
            down();
        }
        while (y + 1 < m) {
            right();
        }

        ans.push_back(path);
    }

    if (g[0][0]) {
        path.clear();
        x = 0, y = 0;
        for (int i = 0; i < n - 1; ++i) {
            down();
        }
        for (int i = 0; i < m - 1; ++i) {
            right();
        }
        ans.push_back(path);
    }

    return true;
}

G

nmd 不会啊。

一开始考虑公式去了,想着能化成什么易于维护的东西,实际上只需要考虑 \(f(i, j)\) 的变化规律就好了。

此题关键的观察就是 \(f(i, j)\) 计算。

想到用字典树去实现新增的操作是容易的,但困难的是,如何用字典树上的信息来计算更新 \(f(i, j)\)。单独考虑字典树上每个节点对答案的贡献,对于一个字典树节点 \(x\),假设有 \(sz_x\) 个字符串经过了该节点,则该节点就能对 \(f(i, j)\) 贡献 \(\lfloor {sz_x \over j } \rfloor\) 的贡献。计算所有节点对 \(f(i, j)\) 的贡献就能得到其值。

我们再来考虑插入一个字符串后的更新。什么时候由\(\lfloor {sz_x \over j } \rfloor\) 变到 \(\lfloor {sz_x + 1 \over j } \rfloor\) 会让值增加 1 呢,即 \(j \mid sz_x + 1\) 时,于是我们找到所有的 \(sz_x + 1\) 的因数 \(k\) 然后让 \(f(i, k)\) 加一更新值并更新答案就好了。

void upd(int val) {
    for (auto &p : fac[val]) {
        ans ^= 1ll * p * f[p];
        f[p] += 1;
        ans ^= 1ll * p * f[p];
    }
    return;
}

void insert(std::string s) {
    int cur = 0;
    for (auto &c : s) {
        int idx = c - 'a';
        if (trie[cur].to[idx] == 0) {
            trie[cur].to[idx] = ++tot;
            trie[tot] = Node();
        }
        cur = trie[cur].to[idx];

        trie[cur].siz += 1;
        upd(trie[cur].siz);
    }
    return;
}
posted @ 2025-10-05 22:18  Young_Cloud  阅读(16)  评论(0)    收藏  举报