HD 2025 春季联赛 4

1001

模拟

就注意理解一下题意,题中 \(k\) 的含义是当一个敌人收到了 \(k\) 次伤害后,如果没死,就进入了不可选中的状态。其他的就没啥了直接看码。

CODE
struct ENEMY {
    int hp, u, idx, cnt;
    bool operator< (const ENEMY &a) const {
        if (hp != a.hp) {
            return hp > a.hp;
        }
        if (u != a.u) {
            return u > a.u;
        }
        return idx > a.idx;
    }
};

void solve()
{
    int n, u, k, hp;
    std::cin >> n >> u >> k >> hp;
    std::priority_queue<ENEMY> attack;
    std::priority_queue<std::pair<int, int>> mx;
    std::vector dead(n + 1, false);
    for (int i = 1; i <= n; i++) {
        ENEMY tmp;
        std::cin >> tmp.u >> tmp.hp;
        tmp.idx = i, tmp.cnt = 0;
        attack.push(tmp);
        mx.push(std::make_pair(tmp.u, i));
    }

    int ans = 0;
    while (hp >= 0 && ans < n) {
        auto cur = attack.top();
        attack.pop();

        cur.hp -= cur.cnt == 0 ? u : (u >> 1);
        cur.cnt++;
        if (cur.hp <= 0) {
            dead[cur.idx] = true;
            ans++;
        }
        else if (cur.cnt < k) {
            attack.push(cur);
        }

        while (not mx.empty() && dead[mx.top().second]) {
            mx.pop();
        }
        if (not mx.empty()) {
            hp -= mx.top().first;
        }
    }
    std::cout << ans << '\n';
}

1002

还没看……

1003

二进制

解题思路

二进制下两个数比较大小,我们从高位往低位看,若相同则往下看,否则就能判断大小了。该题的做法就是基于这个简单直白的性质。

首先我们可以判断 \(x\) 的上界,即使 \(p_x = kx + b\) 的第 61 位为 1 的 \(x\),因为 \(10^{18} < 2^{60} < 2^{61}\)。下界当然就是 0 了。得到了上下界 \([l, r]\),我们就可以从高位往低位逐渐缩小我们的范围 \([l, r]\),并在此过程中计算答案。具体的:

  • 在最高位第 61,我们可以找到两个相邻的数 \(ll\)\(rr\) 使得对于 \(x \in [l, rr]\), \(p_x\) 的第 61 位为 0,对于 \(x \in [l, r]\), \(p_x\) 的第 61 位为 1。此时,\(c\)\(x\) 的第 61 位都为0,所以为了使 \(p_x \oplus c \leq v\),我们将区间缩至 \([l, rr]\)\(p_x \oplus c\) 的第 61 位为 1。
  • 从高位到底位我们可以不断缩减区间,因为只要我们的 \(x\) 在区间内,就一定可以保证高位不变而只变低位。与最高位第 61 位有些许不同的是,在较低位,我们的 \(c\)\(v\) 可能是 1。\(c\) 在当前遍历到的位是 1 与否,只会影响我们选择 \([l, rr]\)\([ll, r]\) 中的那个区间。而 \(v\) 在当前遍历到的位是 1 时,我们就可以把使得 \(p_x \oplus c\) 的当前位位 0 的区间的长度直接加到答案里去,因为在这个区间里,\(p_x \oplus c\)\(v\) 在较高位是相等的,而在当前位 \(p_x \oplus c < v\),所以不管低位如何整体的值就 \(p_x \oplus c < v\)
  • 最后注意以下把遍历完后的区间长度加到答案里面去就好了。
CODE
void solve()
{
    i64 k = 0, b = 0, c = 0, v = 0;
    std::cin >> k >> b >> c >> v;
    i64 ans = 0, l = 0, r = (1ll << 61) / k;
    for (int bit = 61; ~bit; bit--) {
        i64 ll = l, rr = r;
        while (ll <= rr) {
            i64 m = ll + rr >> 1;
            if ((k * m + b) >> bit & 1) {
                rr = m - 1;
            }
            else {
                ll = m + 1;
            }
        }
        std::array<i64, 2> rng[2] = { { l, rr }, { ll, r } };

        int cur = c >> bit & 1;
        if (v >> bit & 1) {
            ans += rng[cur][1] - rng[cur][0] + 1;
            l = rng[cur ^ 1][0];
            r = rng[cur ^ 1][1];
        }
        else {
            l = rng[cur][0];
            r = rng[cur][1];
        }
    }
    std::cout << ans + (r - l + 1) << '\n';
}

1004

代码很简答,但是还没想明白具体是怎么个事

CODE
int ans;
void dfs(int cur, i64 gcd) {
    if (g[cur].empty()) {
        if (__builtin_popcount(gcd) <= 1) {
            ans++;
        }
        return;
    }

    for (auto &to : g[cur]) {
        dfs(to, std::__gcd(gcd, std::abs(a[to] - a[cur])));
    }
}

void solve()
{
    int n = 0;
    std::cin >> n;
    for (int i = 1; i <= n; i++) {
        g[i].clear();
    }
    for (int i = 2; i <= n; i++) {
        int f = 0;
        std::cin >> f;
        g[f].push_back(i);
    }
    for (int i = 1; i <= n; i++) {
        std::cin >> a[i];
    }

    ans = 0;
    dfs(1, 0);
    std::cout << ans << '\n';
}

1005

贪心

解题思路

首先对于打折和减价,我们肯定先选择打折,而且在打折时,我们肯定先使用折扣力度大的;在减价时,我们肯定先使用减价多的。

于是排个序前缀搞一下然后枚举用几次打折就好了取最小值就好了。

CODE
void solve()
{
    int p = 0, n = 0, k = 0;
    std::cin >> p >> n >> k;
    for (int i = 0; i <= n; i++) {
        mul[i] = 1.0,
        sub[i] = 0;
    }
    int c0 = 0, c1 = 0;
    for (int i = 0; i < n; i++) {
        int op = 0, x = 0;
        std::cin >> op >> x;
        if (op == 0) {
            mul[++c0] = 1.0 * x / 10.0;
        }
        else {
            sub[++c1] = x;
        }
    }
    std::sort(mul + 1, mul + 1 + n);
    std::sort(sub + 1, sub + 1 + n, std::greater<i64>());
    for (int i = 1; i <= n; i++) {
        mul[i] *= mul[i - 1];
        sub[i] += sub[i - 1];
    }

    double ans = p;
    for (int i = 0; i <= k; i++) {
        ans = std::min(ans, 1.0 * mul[i] * p - sub[k - i]);
    }
    if (ans < 0.0) {
        ans = 0;
    }
    std::cout << std::fixed << std::setprecision(2) << ans << '\n';
}

1006

树状数组

树状数组秒了

CODE
int n = 0, q = 0;
int a[N + 5];
i64 tr[N + 5];

int lowbit(int x) {
    return x & -x;
}

void add(int pos, i64 val) {
    while (pos <= n) {
        tr[pos] += val;
        pos += lowbit(pos);
    }
    return;
}
i64 ask(int pos) {
    i64 res = 0;
    while (pos >= 1) {
        res += tr[pos];
        pos -= lowbit(pos);
    }
    return res / 100;
}

void solve()
{
    std::cin >> n >> q;
    for (int i = 1; i <= n; i++) {
        tr[i] = 0;
    }
    for (int i = 1; i <= n; i++) {
        std::cin >> a[i];
        add(i, a[i]);
    }
    
    i64 ans = 0;
    int tim = 0;
    while (q--) {
        int op, x, y;
        std::cin >> op >> x >> y;
        if (op == 1) {
            add(x, y - a[x]);
            a[x] = y;
        }
        else {
            ans ^= 1ll * (++tim) * (ask(y) - ask(x - 1));
        }
    }
    std::cout << ans << '\n';
    return;
}

1007

博弈论 NP状态 模拟

解题思路

这道题不是思维博弈,而是需要你去模拟这个游戏然后确定每个游戏局面的状态的。

对于 N态和P态,我们要知道以下几点:

  1. N(Next)态代表后手必胜态,P(precious)态代表先手必胜态。
  2. 对于次态存在N态的状态是P态。
  3. 对于次态中都是P态的状态是N态。

在这题中有显而易见的是N态或者P态的局面,即后两个数是 0 或者前两个数是 0 的局面。

我们可以从这些已经确定的局面出发BFS去考虑那些能够到达这些局面的局面,并不断将确定状态的局面入队。

确定了所有局面的状态后,就可以直接输出答案了。

(此题的代码并不怎么好写)

CODE
// 0 : Uncertain, AKA Tie
// 1 : P
// 2 : N
int status[N];
int cntp[N]; // 统计次态有多少是 P态
std::vector<int> g[N];
std::vector<int> ig[N];

int idx[10][10][10][10];

void solve()
{
    int a = 0, b = 0, c = 0, d = 0;
    std::cin >> a >> b >> c >> d;
    if (a > b) {
        std::swap(a, b);
    }
    if (c > d) {
        std::swap(c, d);
    }
    std::cout << status[idx[a][b][c][d]] << '\n';
}

int main()
{
    IOS;
    int _t = 1;
    std::cin >> _t;
    
    std::queue<int> q;
    auto push = [&](int u, int s) -> void {
        status[u] = s;
        q.push(u);
    };
    auto add = [&](int u, int a, int b, int c, int d) -> void {
        if (c > d) {
            std::swap(c, d);
        }
        int v = idx[a][b][c][d];
        assert(a <= b && c <= d);
        g[u].push_back(v);
        ig[v].push_back(u);
    };
    int cnt = 0;
    // 显然我们有许多等价的局面,如 { a, b, c, d} 和 { b, a, d, c }
    // 此时我们就只取一种
    // 这一段是考虑所有可能到达的不等价的局面并标号
    for (int a = 0; a < 10; a++) {
        for (int b = a; b < 10; b++) {
            for (int c = 0; c < 10; c++) {
                for (int d = c; d < 10; d++) {
                    int u = idx[a][b][c][d] = cnt++;
                    if (a == 0 && b == 0) {
                        push(u, 1);
                    }
                    else if (c == 0 && d == 0) {
                        push(u, 2);
                    }
                }
            }
        }
    }

    // 这一段是考虑所有未分出胜负的局面并寻找其次态
    for (int a = 0; a < 10; a++) {
        for (int b = std::max(a, 1); b < 10; b++) {
            for (int c = 0; c < 10; c++) {
                for (int d = std::max(c, 1); d < 10; d++) {
                    int u = idx[a][b][c][d];
                    // 注意在模拟时,玩家不能使用 0,也不能去使用对方的 0。
                    if (a) {
                        if (c) {
                            add(u, c, d, (a + c) % 10, b);
                        }
                        add(u, c, d, (a + d) % 10, b);
                    }
                    if (c) {
                        add(u, c, d, a, (b + c) % 10);
                    }
                    add(u, c, d, a, (b + d) % 10);
                }
            }
        }
    }
    
    while (not q.empty()) {
        int v = q.front();
        q.pop();

        if (status[v] == 2) { // 能达到N态的状态就直接是P态。
            for (auto &u : ig[v]) {
                if (status[u] == 0) {
                    push(u, 1);
                }
            }
        }
        else {
            for (auto &u : ig[v]) { // 所有次态都是P态的就是N态
                if (status[u] == 0 && ++cntp[u] == g[u].size()) {
                    push(u, 2);
                }
            }
        }
    }

    while (_t--)
    {
        solve();
    }

    sp();

    return 0;
}

1008

DP

不解释直接看码

CODE
void solve()
{
    int n = 0, k = 0;
    std::cin >> n >> k;
    std::vector a(n + 1, std::vector(k + 1, 0));
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= k; j++) {
            std::cin >> a[i][j];
        }
    }

    std::vector f(n + 1, std::vector(k + 1, 0));
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= k; j++) {
            f[i][j] = f[i - 1][j] + a[i][j];
        }
        for (int j = 1; j <= k; j++) {
            f[i][j] = std::max(f[i][j], f[i][j - 1]);
        }
    }
    std::cout << f[n][k] << '\n';
    return;
}

1009

状态压缩

解题思路

首先我们可以用一个 \(n\) 位二进制数来表示有那些英雄可以选。在这个二进制数中,第 \(i\) 位为 1 代表第 \(i\) 位英雄可以选择。于是对于所有的 \(s \in [1,2^n)\) 如果我们提前计算出 \(ans[s]\) 表示可选英雄构成二进制数 \(s\) 时的方案数,就可以快速地回答询问。

首先我们计算出只选择 5 个英雄的方案数,具体如下:
先计算在不考虑分路的情况下,这 5 个人的英雄池能有那些组合,然后再考虑分路,每种英雄组合有共 \(5!\) 种不同的分路选择,看有几种是合法的,如果是 0 种就说明这个英雄组合是不合法的。

得出这个后我们就可以 SOSdp 跑一下算出整个 \(ans\) 数组了。

CODE
int n, q;
std::vector<int> use[5];
int R[N][5];

int ans[1 << N];

void solve()
{
    std::cin >> n >> q;
    for (auto &v : use) {
        int m = 0;
        std::cin >> m;
        v.clear();
        v.assign(m, 0);
        for (auto &i : v) {
            std::cin >> i;
            i--;
        }
    }
    std::fill(ans, ans + (1 << n), 0);
    for (auto &c0 : use[0]) {
        for (auto &c1 : use[1]) {
            for (auto &c2 : use[2]) {
                for (auto &c3 : use[3]) {
                    for (auto &c4 : use[4]) {
                        ans[(1 << c0) | (1 << c1) | (1 << c2) | (1 << c3) | (1 << c4)]++;
                    }
                }
            }
        }
    }
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < 5; j++) {
            std::cin >> R[i][j];
        }
    }
    
    std::vector p(5, 0), b(0, 0);
    std::iota(p.begin(), p.end(), 0);
    for (int s = 0; s < (1 << n); s++) {
        if (std::__popcount(s) != 5) {
            ans[s] = 0;
        }
        else {
            b.clear();
            for (int i = 0; i < n; i++) {
                if (s >> i & 1) {
                    b.push_back(i);
                }
            }
            int mul = 0;
            do
            {
                if (R[b[0]][p[0]] && R[b[1]][p[1]] && R[b[2]][p[2]] && R[b[3]][p[3]] && R[b[4]][p[4]]) {
                    mul++;
                }
            } while (std::next_permutation(p.begin(), p.end()));
            ans[s] *= mul;
        }
    }

    // SOS dp
    for (int i = 0; i < n; i++) {
        for (int s = 0; s < (1 << n); s++) {
            if (s >> i & 1) {
                ans[s] += ans[s ^ (1 << i)];
            }
        }
    }
    
    while (q--) {
        int m = 0, ban = 0;
        std::cin >> m;
        for (int i = 0; i < m; i++) {
            int x = 0;
            std::cin >> x;
            ban |= (1 << x - 1);
        }
        ban ^= (1 << n) - 1;
        std::cout << ans[ban] << '\n';
    }
    return;
}

1010

根号分块

解题思路

我们把给出的序列认为是给节点染色了,序列中的每个值就代表一个颜色。我们并不需要真的按照题目的要求在节点中连边建图,我们只用在颜色之间连边建图就好了。

建好图之后我们就容易想到一种暴力的 DP 做法,即从 1 到 \(n\),每次计算时不仅要计算 dp 数组的答案,还要将计算出来的答案存储到当前的颜色中,于是转移数组中我们只有找与当前颜色连接的颜色的值就好了:

{
    for (int i = 1; i <= n; i++) {
        for (auto &to : g[i]) {
            dp[i] += cnt[to];
        }
    }
    cnt[a[i]] += dp[i];
}

这样做的时间复杂度在最坏的情况下是 \(O(NC)\) 的不能通过此题。想要优化整个复杂度,需要知道在一个有 \(m\) 条边的图中,节点度数大于 \(k\) 的节点有 \(m \over k\) 个(考虑每一个度数大于 \(k\) 的节点,就算每个点只有 \(k\) 边, \({m \over k}\) 个这样的节点就有差不多 \(m\) 条边了)。于是我们尝试分块:

  • 对于度数小于等于 \(k\) 的节点,我们就用以上暴力。
  • 对于度数大于 \(k\) 的节点(记为大点),我们建立这些节点的反图,在 dp 计算答案的过程中,每次都将当前答案加到当前颜色能到达的大点上,要用是就直接取数就好了。

这样做整体的复杂度是 \(O(n(k + {m \over k}))\) 的,显然在 \(k = \sqrt{m}\) 时最优。

CODE
int a[N + 5];
std::vector<int> g[N + 5], h[N + 5];
i64 cnt[N + 5], pre[N + 5];

void solve()
{
    int n = 0, c = 0;
    std::cin >> n >> c;
    for (int i = 1; i <= n; i++) {
        std::cin >> a[i];
    }
    
    int m = 0;
    std::cin >> m;
    for (int i = 1; i <= c; i++) {
        g[i].clear(), h[i].clear();
        cnt[i] = pre[i] = 0;
    }
    for (int i = 0; i < m; i++) {
        // 1 <= u, v <= c
        int u = 0, v = 0;
        std::cin >> u >> v;
        g[u].push_back(v);
        if (u != v) {
            g[v].push_back(u);
        }
    }
    int k = std::sqrt(c);
    for (int i = 1; i <= c; i++) {
        if (g[i].size() > k) {
            for (auto &to : g[i]) {
                h[to].push_back(i);
            }
        }
    }

    for (int i = 1; i <= n; i++) {
        i64 cur = (i == 1 ? 1 : 0);
        int col = a[i];
        if (g[col].size() > k) {
            (cur += pre[col]) %= Mod;
        }
        else {
            for (auto &to : g[col]) {
                (cur += cnt[to]) %= Mod;
            }
        }

        if (i == n) {
            std::cout << cur << '\n';
            return;
        }
        (cnt[col] += cur) %= Mod;
        for (auto &to : h[col]) {
            (pre[to] += cur) %= Mod;
        }
    }
}
posted @ 2025-04-01 23:16  Young_Cloud  阅读(39)  评论(0)    收藏  举报