24 Hongkong B and 2023 ICPC Shenyang

24 Hongkong B and 2023 ICPC Shenyang

24 Hongkong B

我们能造成的伤害范围比较小,考虑从这一点入手。如果每次都造成 1 点伤害,则 \(max\{a_i + b_i\}\) 次后就能击败所有敌人,最后造成 \(max\{a_i + b_i\}\) 点伤害,这是造成总伤害的最小值。在破甲和击杀时,对于非 1 的伤害,都有可能溢出,两次溢出最多溢出 \(2k\) 点伤害,考虑破甲的溢出,当造成的总伤害超过 \(max\{a_i + b_i\} + k\) 时,必然会击败所有对手,但是再考虑击败时的溢出,我们最后造成的总伤害可能会达到 \(max\{a_i + b_i\} + 2k\)。也就是说,我们最后造成的总伤害的范围是小的。

这启事我们可以枚举最后造成的总伤害算最小代价和方案数。具体得,设我们确定造成得总伤害为 \(s\),若当前已造成了 \(x\) 点伤害,下一步我想造成 \(y\) 点伤害,则会对护盾值 \(\in (x, x + y]\) 范围内的敌人造成破甲,此时若想后续伤害 \(s - x - y\) 仍有可能击败所有敌人,就需要 \(s - x - y\) 大于护盾值 \(\in (x, x + y]\) 的敌人的最高血量。于是就可以根据这个约束来 dp

int n, m, k;
int a[N + 5], b[N + 5];
// hp[i] 护盾值为 i 的敌人的最高血量
int hp[S];
i64 cost[K + 5];

// 最小代价、方案数
i64 f[S + 5], g[S +  5];

void add(i64 &a, i64 b) {
    a += b;
    if (a >= Mod) {
        a -= Mod;
    }
}

std::array<i64, 2> get(int s) {
    for (int i = 0; i <= s; ++i) {
        f[i] = Inf;
        g[i] = 0ll;
    }
    f[0] = 0ll;
    g[0] = 1ll;

    for (int i = 0; i <= s; ++i) {
        int mx = 0;
        for (int j = 1; j <= k && i + j <= s; ++j) {
            mx = std::max(mx, hp[i + j]);
            // 剩余的伤害要大于血量
            if (s - (i + j) < mx) {
                break;
            }

            i64 val = f[i] + cost[j];
            if (val == f[i + j]) {
                add(g[i + j], g[i]);
            }
            if (val < f[i + j]) {
                f[i + j] = val;
                g[i + j] = g[i];
            }
        }
    }

    return { f[s], g[s] };
}

void solve() {
    std::cin >> n >> m;
    for (int i = 0; i < n; ++i) {
        std::cin >> a[i];
    }
    for (int i = 0; i < n; ++i) {
        std::cin >> b[i];
    }
    std::cin >> k;
    for (int i = 1; i <= k; ++i) {
        std::cin >> cost[i];
    }

    int mx = 0;
    for (int i = 0; i < n; ++i) {
        mx = std::max(a[i] + b[i], mx);
    }
    for (int i = 1; i <= k + k + mx; ++i) {
        hp[i] = 0;
    }
    for (int i = 0; i < n; ++i) {
        hp[b[i]] = std::max(hp[b[i]], a[i]);
    }

    i64 mn = Inf;
    i64 cnt = 0;
    // 枚举总伤害
    for (int i = mx; i <= k + k + mx; ++i) {
        auto [fs, gs] = get(i);
        if (fs == mn) {
            add(cnt, gs);
        }
        if (fs < mn) {
            mn = fs;
            cnt = gs;
        }
    }
    std::cout << mn << ' ' << cnt << '\n';
}

23 Senyang

E

数据范围非常的小啊,所有的状态,加上状态之间可能的转移也就 \(n^4\),所以直接 bfs 爆搜

void solve() {
    int x = 0, y = 0, p = 0, q = 0;
    std::cin >> x >> y >> p >> q;

    int ans = Inf;
    std::queue<std::array<int, 4>> que;
    que.push({ x, y, 0, 0 });
    vis[x][y][0] = true;
    while (not que.empty()) {
        auto [ux, uy, side, d] = que.front();
        que.pop();
        if (ux == 0 && side == 1) {
            ans = std::min(ans, d);
        }

        // 在家一侧
        if (side == 1) {
            for (int dx = 0; dx + ux <= x; ++dx) {
                for (int dy = 0; dy + uy <= y; ++dy) {
                    if (dx + dy > p) {
                        break;
                    }
                    // 家对岸
                    int vx = ux + dx;
                    int vy = uy + dy;
                    // 家
                    int hx = x - vx;
                    int hy = y - vy;

                    if ((hx != 0 && hy > hx + q) || vis[vx][vy][0]) {
                        continue;
                    }

                    que.push({ vx, vy, 0, d + 1 });
                    vis[vx][vy][0] = true;
                }
            }
        }
        // 在对岸
        else {
            for (int dx = 0; dx <= ux; ++dx) {
                for (int dy = 0; dy <= uy; ++dy) {
                    if (dx + dy > p) {
                        break;
                    }
                    // 家对岸
                    int vx = ux - dx;
                    int vy = uy - dy;
                    // 家
                    int hx = x - vx;
                    int hy = y - vy;

                    if ((vx != 0 && vy > vx + q) || vis[vx][vy][1]) {
                        continue;
                    }

                    que.push({ vx, vy, 1, d + 1 });
                    vis[vx][vy][1] = true;
                }
            }
        }
    }

    std::cout << (ans == Inf ? -1 : ans) << '\n';
}

J

考虑什么样的边不能操作,可以发现与叶子相连的边不能选,对与叶子相连的边镜像进行一次操作相当于交换了叶子和他的父亲,树的形态仍保持不变。其次能够操作的边最多只能操作一次,因为操作一次之后,所选的那条边就与叶子相邻了。所以只用看可操作边的奇偶就好了。

K

能够更新的最多次数是好求的:把所有的正数调整到非正数前面,操作次数就是正数的数量。对于最少次数,只需要把所有非正数放在正数前面,并将正数排成升序,这样能让分数大于 0 后,剩下的正数尽可能少,也就是更新的次数尽可能少,假设正数的数量是 \(x\),对正数升序排序后,第一个让分数大与 0 的正数是 \(k\),则答案就是 \(x - (x - k) = k\),也就是找到 \(k\) 就行了。线段树上二分易解:


struct Info {
    int num;
    i64 sum;
    Info *l, *r;

    Info() : num(0), sum(0), l(nullptr), r(nullptr) {}
    ~Info() {
        if (!l) {
            delete l;
            l = nullptr;
        }
        if (!r) {
            delete r;
            r = nullptr;
        }
    }
} *root = nullptr;

int a[N + 5];

void add(Info *&cur, int l, int r, int pos, int val) {
    if (!cur) {
        cur = new Info();
    }

    cur->num += val;
    cur->sum += 1ll * pos * val;

    if (l == r) {
        return;
    }

    int m = l + r >> 1;
    if (pos <= m) {
        add(cur->l, l, m, pos, val);
    }
    else {
        add(cur->r, m + 1, r, pos, val);
    }
}

int upper_bound(Info *&cur, int l, int r, i64 val) {
    if (!cur) {
        return 0;
    }
    if (l == r) {
        return val / l;
    }

    int lnum = 0;
    i64 lsum = 0ll, rsum = 0ll;
    if (cur->l) {
        lsum = cur->l->sum;
        lnum = cur->l->num;
    }
    if (cur->r) {
        rsum = cur->r->sum;
    }

    int m = l + r >> 1;
    if (lsum > val) {
        return upper_bound(cur->l, l, m, val);
    }
    else {
        return lnum + upper_bound(cur->r, m + 1, r, val - lsum);
    }
}

void solve() {
    int n = 0, q = 0;
    std::cin >> n >> q;
    i64 neg = 0;
    for (int i = 1; i <= n; ++i) {
        std::cin >> a[i];
        if (a[i] > 0) {
            add(root, 1, Inf, a[i], 1);
        }
        else {
            neg -= a[i];
        }
    }

    while (q--) {
        int idx = 0, val = 0;
        std::cin >> idx >> val;
        if (a[idx] > 0) {
            add(root, 1, Inf, a[idx], -1);
        }
        else {
            neg += a[idx];
        }
        a[idx] = val;
        if (a[idx] > 0) {
            add(root, 1, Inf, a[idx], 1);
        }
        else {
            neg -= a[idx];
        }

        std::cout << upper_bound(root, 1, Inf, neg) + 1 << '\n';
    }
}
posted @ 2025-10-18 00:59  Young_Cloud  阅读(11)  评论(0)    收藏  举报