2025“钉耙编程”中国大学生算法设计春季联赛(4)题解

1001 战斗爽

题解:

纯模拟,用一个heap和set去记录剩下的怪物中的攻击力最高的和还没有收到超过k次伤害的

cpp 复制代码
struct enemy{
    int atk, hp, cnt, id;
    bool operator> (const struct enemy& w) const {
        if (hp == w.hp) {
            if (atk == w.atk) {
                return id > w.id;
            } else {
                return atk > w.atk;
            }
        } else {
            return hp > w.hp;
        }
    }
};

void solve()
{
    int n, u, k, hq;
    cin >> n >> u >> k >> hq;
    vector<enemy> e(n + 1);
    priority_queue<enemy, vector<enemy>, greater<enemy>> heap;
    set<PII> S;
    for (int i = 1; i <= n; i++) {
        int a, b;
        cin >> a >> b;
        e[i] = {a, b, 0, i};
        heap.push(e[i]);
        S.insert({a, i});
    }

    int ans = 0;
    while(S.size() && heap.size()) {
        if (hq <= 0) break;
        auto [atk, hp, cnt, id] = heap.top();
        heap.pop();

        hp -= (cnt == 0 ? (int)u : u / 2);
        if (hp <= 0) {
            S.erase({atk, id});
            ans++;
        } else if (cnt + 1 == k) {
        } else {
            heap.push({atk, hp, cnt + 1, id});
        }

        if (!S.empty()) {
            auto it = S.end();
            it--;
            hq -= it->first;
        }
    }

    cout << ans << endl;
}

1003 洞察

题解:

跟上一场的位运算题目的思考方式好像,都是从高位向低位进行枚举去划分。

首先明确一个性质:a ^ b = c ------> a = b ^ c

依据上述性质,我们可以把不等式 px ^ c <= v 转化为 px <= v ^ c,这里不要考虑不等式,需要考虑的是对于每一个 px ^ c 总有一个 v ^ c 来进行映射,所以可以这么转化。

然后,我们的任务就转化成了,求出有多少个 px 是在 1 ~ v ^ c 的范围内的,这里应该怎么计算呢??

跟上一场的1001一样,我们从高位到低位枚举,假设目前枚举到 i 位,则按照从 i ~ 最高位 都贴近上界的方式来进行集合的划分,如果第i位为1,则这一位取0,然后低位随便取,一定满足在 1 ~ v^c 的范围内。

知道了一个范围,知道了x的表达式,我们按照等差数列的求和公式,以及前缀和的公式,就可以知道一个区间内的 x 的个数。

cpp 复制代码
void solve()
{
    int k, b, c, v;
    cin >> k >> b >> c >> v;
    int led = 0, ans = 0;

    if ((v ^ c) - b >= 0 && ((v ^ c) - b) % k == 0) ans++;
    for (int i = 59; i >= 0; i--){
        if (v >> i & 1) {
            int u = (led ^ c) & ((1LL << 60) - (1LL << i));
            int l = u - 1, r = u + ((1LL << i) - 1);
            if (r >= b) {
                ans += (r - b) / k + 1;
            } 
            if (l >= b) {
                ans -= (l - b) / k + 1;
            }
        }
        led |= (v >> i & 1) << i;
    }
    cout << ans << endl;
}

这里有几个位运算的操作解释一下

cpp 复制代码
int u = (led ^ c) & ((1LL << 60) - (1LL << i));

led 表示的就是之前位全部贴近上界的值

((1LL << 60) - (1LL << i)) 表示二进制表示下,当前位及高位都为1,低位均为0的一个数字,然后和之前的数字取&就表示低位全部取0,高位保留。

而且由于led还没有更新,所以本位i也是取0的,则当前区间就是 [u, u + (1LL << i ) - 1],后者就是后面所有位都取1的情况,然后计算就行了。

1005 持家

感觉最签的一题

题解:

先用打折券肯定更优,然后直接枚举所有方案就行了,用一个前缀积和一个前缀和来加速枚举过程

cpp 复制代码
void solve()
{   
    double p;
    int n, k;
    cin >> p >> n >> k;
    vector<double> a(1), b(1);
    for (int i = 1; i <= n; i++) {
        int op, x;
        cin >> op >> x;
        if (op == 0) {
            a.push_back(x);
        } else {
            b.push_back(x);
        }
    }
    sort(a.begin() + 1, a.end());
    sort(b.begin() + 1, b.end(),  greater<double>());
    vector<double> prea(n + 1), preb(n + 1, 0);

    prea[0] = 1;
    for (int i = 1; i < b.size(); i++) {
        preb[i] = preb[i - 1] + b[i];
    }
    for (int i = 1; i < a.size(); i++) {
        prea[i] = prea[i - 1] * a[i] / 10.0;
    }

    double ans = max(p - preb[min((int)b.size() - 1LL, (int)k)], 0.00);
    for (int i = 0; i <= k; i++) {
        if (i >= a.size()) break;
        ans = min(ans, max((double)p * prea[i] - preb[min((int)(b.size() - 1LL), (int)k - i)], 0.00));
    }
    printf("%.2lf\n", ans);
}

1006 进步

题解:

树状数组模板题

cpp 复制代码
template <typename T>
class Fenwick {
public:
    int n;
    std::vector<T> a;
    Fenwick(int n_ = 0) {
        init(n_);
    }
    void init(int n_) {
        n = n_;
        a.assign(n + 1, T{});
    }
    void add(int x, const T &v) {
        for (int i = x; i <= n; i += i & -i) {
            a[i] = (a[i] + v);
        }
    }
    // 查询位置 [1, x] 的前缀和
    T sum(int x) {
        T ans{};
        for (int i = x; i > 0; i -= i & -i) {
            ans = (ans + a[i]);
        }
        return ans;
    }
    // 查询区间 [l, r] 的区间和
    T rangeSum(int l, int r) {
        return sum(r) - sum(l - 1);
    }
    int select(const T &k) {
        int x = 0;
        T cur{};
        for (int i = 1 << std::__lg(n); i; i /= 2) {
            if (x + i <= n && cur + a[x + i] <= k) {
                x += i;
                cur = cur + a[x];
            }
        }
        return x;
    }
};

void solve()
{
    int n, q;
    cin >> n >> q;
    vector<int> a(n + 1);
    Fenwick<int> fen(n);
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        fen.add(i, a[i]);
    }

    int ans = 0, cnt = 1;
    for (int i = 1; i <= q; i++) {
        int op, x, y;
        cin >> op >> x >> y;
        if (op == 1) {
            fen.add(x, -(a[x]));
            a[x] = y;
            fen.add(x, a[x]);
        } else if (op == 2) {
            int sl = fen.sum(x - 1), sr = fen.sum(y);
            int res = sr / 100 - sl / 100;
            ans ^= (cnt * res);
            cnt++;
        }
    }

    cout << ans << endl;
}

1008 制衡

题解:

WA了三发的DP

cpp 复制代码
void solve()
{
    int n, k;
    cin >> n >> k;
    vector<vector<int>> a(n + 1, vector<int>(k + 1));
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= k; j++) {
            cin >> a[i][j];
        }
    }
    vector<vector<int>> f(n + 1, vector<int>(k + 1, -INF));
    f[0][0] = f[0][1] = 0;
    for (int i = 1; i <= n; i++) f[i][1] = f[i - 1][1] + a[i][1];
    for (int i = 1; i <= n; i++) {
        vector<int> pre(k + 1, -INF);
        for (int j = 1; j <= k; j++) {
            pre[j] = max(pre[j - 1], f[i - 1][j]);
        }
        for (int j = 2; j <= k; j++) {
            f[i][j] = pre[j] + a[i][j];
        }
    }
    int ans = 0;
    for (int i = 1; i <= k; i++) {
        ans = max(ans, f[n][i]);
    }
    cout << ans << endl;
}

1010 图之图

题解:

这种题之前基本没有见过,思路就感觉很......假。

考察的是根号分块方面的知识。

cpp 复制代码
void solve()
{
    int n, m, c;
    cin >> n >> c;
    vector<int> a(n + 1);
    for (int i = 1; i <= n; i++) cin >> a[i];
    vector<int> g(c + 1, 0), f(n + 1, 0), w(c + 1, 0);
    vector<vector<int>> e(c + 1), eh(c + 1);

    f[1] = 1;
    cin >> m;
    for (int i = 0; i < m; i++) {
        int u, v;
        cin >> u >> v;
        e[u].push_back(v);
        if (u != v) e[v].push_back(u);
    }

    int S = sqrt(m);
    for (int u = 1; u <= c; u++) {
        for (auto v : e[u]) {
            if (e[v].size() > S) {
                eh[u].push_back(v);
            }
        }
    }
    for (int i = 1; i <= n; i++) {
        (f[i] += w[a[i]]) %= mod;
        for (auto v : eh[a[i]]) {
            (f[i] += g[v]) %= mod;
        }
        (g[a[i]] += f[i]) %= mod;
        if (e[a[i]].size() <= S) {
            for (auto v : e[a[i]]) {
                (w[v] += f[i]) %= mod;   // 出度,所有指向的边都++
            }
        }
    }
    cout << f[n] << endl;
}
相关推荐
奋进的小暄10 分钟前
贪心算法(13)(java)合并区间
算法
快来卷java22 分钟前
深入剖析雪花算法:分布式ID生成的核心方案
java·数据库·redis·分布式·算法·缓存·dreamweaver
阿巴~阿巴~24 分钟前
C/C++蓝桥杯算法真题打卡(Day11)
算法
tpoog28 分钟前
[MySQL]数据类型
android·开发语言·数据库·mysql·算法·adb·贪心算法
刚入门的大一新生1 小时前
排序算法3-交换排序
算法·排序算法
董董灿是个攻城狮2 小时前
Transformer 通关秘籍7:词向量的通俗理解
算法
卷卷的小趴菜学编程2 小时前
算法篇-------------双指针法
c语言·开发语言·c++·vscode·算法·leetcode·双指针法
komo莫莫da2 小时前
Day14 动态规划(3)
算法·深度优先·动态规划
地平线开发者3 小时前
【征程 6】工具链 VP 示例为什么能运行
算法·自动驾驶
ElseWhereR3 小时前
困于环中的机器人
c++·算法·leetcode