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

比赛链接

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

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

开题 + 补题情况

最近一直在准备期中考试,好久没写代码了,这场有点糖,前一个小时一题没开,实属红温,尤其是 1007,想了好久好久。

下周打南昌,自己第一次参加全国性的比赛,希望自己加油发挥吧!

1010 - 绳子切割

此题考查并查集(实际暴力也可以)。

我们删点是从大到小删,那么倒过来就是加点,加点就是从小到大加。

对于每一个点,连向比它小的结点的连通块,看是否能和 \(0\) 在同一块,如果不在,那么直接输出 \(0\),因为这个时候,它不和横梁同属一个连通块,说明它到 \(0\) 之间有点被删除了,进而导致该点被提前删除了。
点击查看代码

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 DSU {
    std::vector<int> fa;

    DSU(int n) {
        fa.resize(n + 1);
        std::iota(fa.begin(), fa.end(), 0);
    }

    int root(int x) {
        return (fa[x] == x) ? x : (fa[x] = root(fa[x]));
    }

    void merge(int u, int v) {
        if(root(u) == 0) {
            fa[root(v)] = root(fa[u]);
        } else {
            fa[root(u)] = root(fa[v]);
        }
    }
};

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

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

    for(int i = 1;i <= m;i ++) {
        int u, v;std::cin >> u >> v;

        g[u].push_back(v);
        g[v].push_back(u);
    }

    for(int i = 1;i <= n;i ++) {
        for(auto &x : g[i]) {
            if(i < x)continue;
            dsu.merge(i, x);
        }

        if(dsu.root(i) != 0) {
            std::cout << 0 << '\n';
            return;
        }
    }

    std::cout << 1 << '\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;
}

1004 - 储值购物

这个题,先上结论,每次取 \(W / 2 + 1\),一定最优。

简单证明如下:

若某次取了 \(\leq W / 2\),那么这次剩下的,一定 \(\geq W / 2\),那么,当下次取 \(W / 2\) 的时候,会继续使用当前这个卡,若取更多,则可以均摊给上次取的,这样的话最后二者都会取到 \(W / 2 + 1\)。

因此,每次取 \(W / 2 + 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;

template<typename T>
T up(T x, T y) {
    return (x + y - 1) / y;
}

void solve()
{
    int v, w;std::cin >> v >> w;
    int h = w / 2 + 1;
    int l = w - h;

    int sum = v / h;
    v -= sum * h;

    int ans = 0;
    if(sum) {
        if(v > l)ans = sum + 1;
        else ans = sum;
    } else {
        ans = 1;
    }

    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;
}

1005 - 真爱口上

这个题看似是一个非常恶心的模拟题,但是题目有一个很重要的信息:所有的字符串都是符合规则的音节序列。也就意味着我们不需要判断串的合法性,只需要根据特征计数就行了。

  • 基本音节结构:不难发现,一定是以元音结尾,因此只需要计算元音有多少个即可。
  • 鼻音:单算 nn 相连的情况,特别注意比如 nnna 的情况,此时不能算为两个鼻音。
  • 促音:计算 pp tt kk ss 的数量即可。

点击查看代码

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;

bool isyun(char c) {
    return c == 'a' || c == 'i' || c == 'u' || c == 'e' || c == 'o';
}

bool isptks(char c) {
    return c == 'p' || c == 't' || c == 'k' || c == 's';
}

int getans(std::string &s) {
    int n = s.size();

    int res = 0;
    int pre = -1;
    for(int i = 0;i < n;i ++) {
        if(isyun(s[i])) {
            res ++;
        }

        if(isptks(s[i])) {
            if(i - 1 >= 0 && s[i - 1] == s[i]) {
                res ++;
            }
        }

        if(s[i] == 'n') {
            if(i - 1 >= 0 && s[i - 1] == s[i] && pre != i - 1) {
                res ++;
                pre = i;
            }
        }
    }

    return res;
}

void solve()
{
    std::string s, t;std::cin >> s >> t;

    std::cout << getans(s) << ' ' << getans(t) << '\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;
}

1007 - 扑克洗牌

你没有看错,这是我过的......第五个题......

真的红温了,这题一直没想到思路,但是过的人又那么多,后面猛地想到了。

首先要明晰一件事,对于移动了的牌,它一定是移动且仅移动一次!

为什么?

因为我们可以把它插入任何位置,那么我们就可以直接把它插入到应该处在的位置,并且不要去动它。

然后呢,我们就要考虑,对于目标序列,我们要如何移动得到。

对于原串,所有数字都是连续的,相邻的。

对于移动,是只能从两头进行移动的。

那么就可以得出以下结论:我们最终的目标串,一定是原串某个连续子串,插入两边的扑克牌得到的。

对于原串中的这个子串,它是不需要移动的,因为它是被插入的对象。

所以要让移动次数最小,那么就是要让选择的这个原串子串长度,尽可能的大。

这个长度,我们用简单的 dp 可以很快计算出来。

记 \(dp_i\) 为以数字 \(i\) 结尾的最长连续子序列的长度。

那么有以下转移:

  • 当 \(i - 1\) 没有出现过时,\(dp_i = 1\)。
  • 当 \(i - 1\) 出现过时,\(dp_i = dp_{i - 1} + 1\)。

对每个 \(dp_i\) 执行 \(n - dp_i\),对所有答案取最小值即可。
点击查看代码

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;std::cin >> n;

    std::vector<int> a(n + 1);
    std::vector<int> vis(n + 1);
    std::vector<int> dp(n + 1);

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

    vis[0] = true;
    for(int i = 1;i <= n;i ++) {
        if(vis[a[i] - 1]) {
            dp[a[i]] = dp[a[i] - 1] + 1;
        } else {
            dp[a[i]] = 1;
        }

        vis[a[i]] = true;
    }

    int ans = inf32;
    for(int i = 1;i <= n;i ++) {
        ans = std::min(ans, n - dp[i]);
    }

    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 - 折线绘制

马拉车算法板子题,只不过从回文关系变成了中心对称关系。

为了方便计数,可以插入无关数字 \(-1\) 来把奇数长度区间和偶数长度区间的情况统一化。
点击查看代码

cpp 复制代码
#include <bits/stdc++.h>
#define inf32 1e9
#define inf64 2e18
#define int long long
#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;std::cin >> n;
    std::vector<int> a(n);

    for(auto &x : a) {
        std::cin >> x;
    }

    std::vector<int> b(2 * n + 1);

    int idx = 0;
    b[idx ++] = -1;
    for(auto &x : a) {
        b[idx ++] = x;
        b[idx ++] = -1;
    }

    n = (int)b.size();

    std::vector<int> d(n);

    auto check = [&](int x, int y, int sum) -> bool {
        if(x < 0 || y >= n) {
            return false;
        }

        if(b[x] == -1 && b[y] == -1) {
            return true;
        }  

        if(b[x] + b[y] == sum) {
            return true;
        }

        return false;
    };

    int ans = 0;
    for(int i = 0, l = 0, r = -1;i < n;i ++) {
        int k = ((i > r) ? 1 : std::min(r - i + 1, d[l + r - i]));
        int now = -1;
        if(b[i] == -1 && i - 1 >= 0 && i + 1 < n) {
            now = b[i - 1] + b[i + 1];
        }

        if(b[i] != -1) {
            now = b[i] * 2;
        }

        while(check(i - k, i + k, now)) {
            k ++;
        }

        d[i] = k --;

        if(i + k > r)l = i - k, r = i + k;

        if(b[i] == -1) {
            ans += k / 2;
        } else {
            ans += d[i] / 2;
        }
    }

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