【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(8)

比赛链接

本文发布于博客园,会跟随补题进度实时更新,若您在其他平台阅读到此文,请前往博客园获取更好的阅读体验。

跳转链接:https://www.cnblogs.com/TianTianChaoFangDe/p/18848680

开题 + 补题情况

这场难度不算很大,但有点吃思维,感觉想出来了的话代码量都不大。

1007 - 架子鼓

不难发现,只需要对所有数字乘上 \(q\) 的数据范围的最小公倍数,就可以把所有分数转化为整数了,然后就可以用 \(map\) 来记录其中一个,查询另外一个来得到同时击打的时刻数量了。
点击查看代码

cpp 复制代码
#include <bits/stdc++.h>
#define inf32 1e9
#define inf64 2e18
#define ls o << 1
#define rs o << 1 | 1

using i64 = long long;
using u64 = unsigned long long;
using u32 = unsigned int;

const int N = 2e5 + 9;

i64 lc;

i64 lcm(i64 x, i64 y) {
    return x / std::gcd(x, y) * y;
}

void solve()
{
    int n, m;std::cin >> n >> m;

    std::vector<i64> a(n), b(m);

    for(int i = 0;i < n;i ++) {
        i64 p, q;std::cin >> p >> q;
        p = p * lc / q;
        a[i] = p;
    }

    for(int i = 0;i < m;i ++) {
        i64 p, q;std::cin >> p >> q;
        p = p * lc / q;
        b[i] = p;
    }

    std::map<i64, bool> vis;
  
    i64 sum = 0;
    for(int i = 0;i < n;i ++) {
        vis[sum] = true;
        sum += a[i];
    }

    i64 ans = 0;
    sum = 0;
    for(int i = 0;i < m;i ++) {
        if(vis.count(sum))ans ++;
        sum += b[i];
    }

    std::cout << ans << '\n';
}

/*1,2,3,4,6,8,16*/

int main()
{
    std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);

    lc = lcm(1, 2);
    lc = lcm(lc, 3);
    lc = lcm(lc, 4);
    lc = lcm(lc, 6);
    lc = lcm(lc, 8);
    lc = lcm(lc, 16);

    int t = 1;std::cin >> t;
    while(t --)solve();

    return 0;
}

1003 - 独到寒山顶

一开始没注意到可以往下走,以为是一个 DP,结果一看可以往下走,并且数据范围乘起来也就 \(250000\),那不就直接 \(BFS\) 最短路嘛。
点击查看代码

cpp 复制代码
#include <bits/stdc++.h>
#define inf32 1e9
#define inf64 2e18
#define ls o << 1
#define rs o << 1 | 1

using i64 = long long;
using u64 = unsigned long long;
using u32 = unsigned int;

const int N = 2e5 + 9;

struct Node {
    int x, y, d;
};

void solve()
{
    int n, m;std::cin >> n >> m;
    std::vector a(n + 2, std::vector<int>(m + 2));
    std::vector vis(n + 2, std::vector<int>(m + 2));

    for(int i = 1;i <= n;i ++) {
        int k;std::cin >> k;
        for(int j = 1;j <= k;j ++) {
            int x;std::cin >> x;
            a[i][x] = true;
        }
    }

    std::queue<Node> q;

    for(int i = 1;i <= n;i ++) {
        if(a[i][1] == 1)continue;
        q.push({i, 1, 1});
    }

    while(q.size()) {
        auto [x, y, d] = q.front();
        q.pop();

        if(vis[x][y])continue;
        vis[x][y] = true;

        if(y == m) {
            std::cout << d << '\n';
            return;
        }

        if(x - 1 >= 1 && a[x - 1][y] != 1) {
            q.push({x - 1, y, d + 1});
        }

        if(x + 1 <= n && a[x + 1][y] != 1) {
            q.push({x + 1, y, d + 1});
        }

        if(y + 1 <= m && a[x][y + 1] != 1) {
            q.push({x, y + 1, d + 1});
        }

        if(y - 1 >= 1 && a[x][y - 1] != 1) {
            q.push({x, y - 1, d + 1});
        }
    }
}

int main()
{
    std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);

    int t = 1;std::cin >> t;
    while(t --)solve();

    return 0;
}

1001 - 拼图游戏

对于这个题,我们可以考虑 \(O(n \log n)\) 的做法,我们枚举 \(x\) 的大小,由于只要有一个 \(y\) 满足了题意,则更大的 \(y\),也一定满足,因此可以二分最小满足的 \(y\) 值。

至于颜色,我们可以用一个 \(bitset\) 来记录,这样的话,只需要调用 \(bitset\) 的 \(all()\) 函数就可以判断是否所有颜色都出现过了,不过要注意,由于 \(bitset\) 的大小是在编译时就决定了的,不可变的,因此只能直接往最大了开,因此要预先把不合法的颜色设置为 \(1\)。
点击查看代码

cpp 复制代码
#include <bits/stdc++.h>
#define inf32 1e9
#define inf64 2e18
#define ls o << 1
#define rs o << 1 | 1

using i64 = long long;
using u64 = unsigned long long;
using u32 = unsigned int;

const int N = 2e5 + 9;

void solve()
{
    int n, m, k;std::cin >> n >> m >> k;
    std::vector a(m, std::vector<int>(n));

    for(int j = 0;j < n;j ++) {
        for(int i = 0;i < m;i ++) {
            std::cin >> a[i][j];
        }
    }

    std::vector<std::bitset<2001>> pre(m);
    for(int i = 0;i < m;i ++) {
        pre[i].set(0, 1);
        for(int j = k + 1;j <= 2000;j ++) {
            pre[i].set(j, 1);
        }
    }

    i64 ans = 0;
    for(int j = 0;j < n;j ++) {
        for(int i = 0;i < m;i ++) {
            pre[i] = pre[i].set(a[i][j], 1);
            if(i - 1 >= 0)pre[i] = pre[i] | pre[i - 1];
        }

        int l = -1, r = m;
        while(l + 1 < r) {
            int mid = l + r >> 1;
            if(pre[mid].all())r = mid;
            else l = mid;
        }

        ans += m - r;
    }

    std::cout << ans << "\n";
}

int main()
{
    std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);

    int t = 1;std::cin >> t;
    while(t --)solve();

    return 0;
}

1002 - 缓存系统

01 背包板题,由于数据出锅导致众多人最后才做出来。

这里和 01 背包板子题唯一不同就是,对于每一行,有很多个不同的前缀取值,但是只能取一个,因此,转移的时候,不能把行这个维度省略掉,要从上一行转移过来。

除此之外,还不要忘了不选这一行的任何值直接继承上一行的情况(本人因为这个被卡了 \(20\) 分钟)。
点击查看代码

cpp 复制代码
#include <bits/stdc++.h>
#define inf32 1e9
#define inf64 2e18
#define ls o << 1
#define rs o << 1 | 1
#define int long long

using i64 = long long;
using u64 = unsigned long long;
using u32 = unsigned int;

const int N = 2e5 + 9;

void solve()
{
    int n, m, x;std::cin >> n >> m >> x;

    std::vector a(n + 1, std::vector<int>(m + 1)); // 空间
    std::vector b(n + 1, std::vector<int>(m + 1)); // 次数
    std::vector dp(n + 1, std::vector<int>(x + 1));

    int sum = 0;
    for(int i = 1;i <= n;i ++) {
        for(int j = 1;j <= m;j ++) {
            std::cin >> a[i][j] >> b[i][j];
            sum += b[i][j];
            a[i][j] += a[i][j - 1];
            b[i][j] += b[i][j - 1];
        }
    } 

    for(int i = 1;i <= n;i ++) {
        for(int j = 1;j <= m;j ++) {
            for(int k = 0;k <= x;k ++) {
                dp[i][k] = std::max(dp[i][k], dp[i - 1][k]);
            }
            for(int k = x;k - a[i][j] >= 0;k --) {
                dp[i][k] = std::max(dp[i - 1][k - a[i][j]] + b[i][j], dp[i][k]);
            }
        }
    }

    int ans = 0;
    for(int i = 1;i <= n;i ++) {
        for(int j = 0;j <= x;j ++) {
            ans = std::max(ans, dp[i][j]);
        }
    } 

    std::cout << sum - ans << '\n';
}

signed main()
{
    std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);

    int t = 1;std::cin >> t;
    while(t --)solve();

    return 0;
}

1010 - 字符串哈希(补题)

这个题,还真要用上字符串哈希的一些思考方向才能 get 到出题人想让你干什么。

分别分析一下 \(A(s)\) 和 \(B(s)\) 有什么性质。

  • 由于 \(A(s)\) 是没有取模的,因此每一个 \(A(s)\) 的值,对应唯一的一个字符串。
  • 由于 \(B(s)\) 对 \(10007\) 取模了,所以 \(B(s)\) 的取值范围被限制在了 \([0, 10007)\)。

那么,我们可以枚举 \(B(s)\) 的值呀!我们枚举 \(B(s)\) 的值,并且按照计算公式计算出 \(A(s)\) 的值,再用 \(A(s)\) 的值还原出字符串的 \(ord\) 序,判断是否合法(是否超出长度范围或者是否出现了 \(0\) 作为 \(ord\)),如果合法,再按 \(B(s)\) 的规则还原出 \(B(s)\) 的值,判断是否和枚举的 \(B(s)\) 的值相同,如果相同,则答案 \(+1\)。
点击查看代码

cpp 复制代码
#include <bits/stdc++.h>
#define inf32 1e9
#define inf64 2e18
#define ls o << 1
#define rs o << 1 | 1
#define int long long

using i64 = long long;
using u64 = unsigned long long;
using u32 = unsigned int;

const int N = 2e5 + 9;
const int M = 10007;

void solve()
{
    int k, c, d, e, f;std::cin >> k >> c >> d >> e >> f;

    std::vector<int> ci(16);

    ci[0] = 1;
    for(int i = 1;i <= 15;i ++) {
        ci[i] = (ci[i - 1] * 10) % M;
    }

    int ans = 0; 
    std::vector<int> tmp;
    for(int i = 0;i < M;i ++) {
        tmp.clear();
        __int128_t now = c * i * i * i + d * i * i + e * i + f;

        while(now) {
            tmp.push_back((int)(now % 27));
            now /= 27;
        }

        if(tmp.size() > k)continue;
        if(tmp.size() == 0)continue;

        bool ck = false;
        int p = 0;
        for(int i = 0;i < tmp.size();i ++) {
            if(tmp[i] == 0)ck = true;
            p = (p + tmp[i] * ci[i]) % M;
        }

        if(ck)continue;
        if(p == i) {
            ans ++;
        }
    }

    std::cout << ans << '\n';
}

signed main()
{
    std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);

    int t = 1;std::cin >> t;
    while(t --)solve();

    return 0;
}

1005 - 咒语附魔(补题)

听说是 CF 的一个原题()

这个题,一开始拿 \(bitset\) 瞎搞了一发,意料之内地 TLE 了。

这个题,有一个你以为只是出题人偷懒但实际上就是做题的关键的信息:B 中每个位置上的数字等概率地生成为 \(0\) 或 \(1\)。

这意味着什么?

首先想一下,要想让结果尽可能地大,那是不是就是前缀连续 \(1\) 的长度尽可能地长?

那么,我们要找的,就是连续的前缀 \(1\) 的长度,又因为 B 中每个位置上的数字等概率地生成为 \(0\) 或 \(1\),所以,每次都有 \(1/2\) 的概率,在该位置异或得到 \(0\),也就会跳出循环,那么,一定会很快就会跳出循环,所以循环次数一定不会多,也就是......暴力就行了。

找到最长的前缀 \(1\) 的长度,对这些串收集起来,排序取最大,数 \(1\) 的个数即可。
点击查看代码

cpp 复制代码
#include <bits/stdc++.h>
#define inf32 1e9
#define inf64 2e18
#define ls o << 1
#define rs o << 1 | 1

using i64 = long long;
using u64 = unsigned long long;
using u32 = unsigned int;

const int N = 2e5 + 9;

void solve()
{
    int n, m;std::cin >> n >> m;

    std::string s, t;std::cin >> s >> t;

    std::vector<std::vector<int>> g(n + 1);

    for(int i = 0;i + n - 1 < t.size();i ++) {
        int sum = 0;
        for(int j = i;j < t.size() && sum < n;j ++) {
            if(t[j] != s[j - i])sum ++;
            else break;
        }
        g[sum].push_back(i);
    }

    std::vector<std::string> ans;

    for(int i = n;i >= 0;i --) {
        if(g[i].size()) {
            for(auto &j : g[i]) {
                std::string tmp = "";
                for(int k = j;k <= j + n - 1;k ++) {
                    tmp += ((t[k] == s[k - j]) ? '0' : '1');
                }
                ans.push_back(tmp);
            }
            break;
        }
    }

    std::sort(ans.begin(), ans.end());
    int sum = 0;
    
    if(!ans.size()) {
        std::cout << 0 << '\n';
    } else {
        for(auto &i : ans[ans.size() - 1]) {
            sum += (i == '1');
        }
        std::cout << sum << '\n';
    }
}

int main()
{
    std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);

    int t = 1;std::cin >> t;
    while(t --)solve();

    return 0;
}
相关推荐
天天超方的15 天前
【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(6)
做题记录
天天超方的23 天前
【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(5)
做题记录
天天超方的1 个月前
【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(4)
做题记录
天天超方的1 个月前
【CF比赛记录】Codeforces Round 1013 (Div. 3)
cf·做题记录
天天超方的1 个月前
【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(2)
做题记录
天天超方的2 个月前
【CF VP记录】Codeforces Round 1008 (Div. 2)
做题记录
天天超方的2 个月前
【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
做题记录
天天超方的2 个月前
Codeforces Round 1007 (Div. 2) 比赛记录
cf·做题记录