sust2024天梯选拔赛题解

天梯选拔赛个人题解

题目照做题顺序书写,大致为难度排列
题目链接:传送门

A

单纯的签到题

回复的邮件的数目为收到的材料数目和ppt数目的总和

因此输出 x + y即可

L

要求算出最多的得分,做出简单题目得10分,中等20,难题30

且根据题目难度,做出题目数目非递增‘

可以讨论,但是数据范围在0-9,即使$O(n^3)$的枚举也不会超时,所以直接枚举即可

void solve()
{
    int n;
    cin >> n;
    ll ans = 0;
    for (int i = 1; i <= n; i++)
        for (int j = 0; j <= n; j++)
            for (int k =0; k <= n; k++){
                if(i < j || j < k || i < k || i + j + k > n)
                    continue;
                ll cnt = 10 * i + 20 * j + 30 * k;
                ans = cnt > ans ? cnt : ans;
            }
    cout << ans;

    return; 
}

H

简要题意:咖啡1元,消费满y元,返现1元。请问x元能喝多少天咖啡

还是先看数据范围2~1e3,直接模拟就能解决

void solve()
{
    int x, y;
    while(cin>>x>>y && x && y){//判断输入是否合法
        ll cnt = 0;//统计喝了多少杯咖啡
        while(x > 0){
            x--;
            cnt++;
            if(cnt % y == 0)//。当咖啡数目够y的时候,返现1元
                x++;
        }
        cout << cnt << endl;
    }
    return;
}

F

原本是一道cf的div 2 D,难度为省选的线段树,但是放宽了时限,使得暴力可以通过。

于是乎,直接按照题目模拟即可。

void solve()
{
    int n, m;
    cin >> n >> m;
    vector<int> v(n + 1);
    for (int i = 1; i <= n; i++)
        cin >> v[i];
    while (m--)
    {
        int op;
        cin >> op;
        if (op == 1)
        {
            ll l, r;
            cin >> l >> r;
            ll ans = 0;//不开ll可能会爆
            for (int i = l; i <= r; i++)
                ans += v[i];
            cout << ans << endl;
        }
        else if (op == 2)
        {
            ll l, r, x;
            cin >> l >> r >> x;
            for (int i = l; i <= r; i++)
                v[i] %= x;
        }
        else
        {
            int k, x;
            cin >> k >> x;
            v[k] = x;
                }
    }
    return;
}

G

这道题实际上是一道经典结论,如果不知道,手推样例也可以guess到(bushi

image

这个点的位置在所有的点的最中间的点时,所有点到这个点的距离最短

具体证明可以参考这个🔗

但是如果n为偶数时,应该取较大的数字是更优的

按照这个思路,直接写代码就可以了

void solve()
{
    int n;
    cin >> n;
    vector<int> v(n + 1);
    for (int i = 1; i <= n; i++)
        cin >> v[i];
    sort(v.begin() + 1, v.end());
    ll pos = n / 2 + 1;//向上取整中位数的位置
    //也就是处理偶数时,如何取到更大的数字
    ll cnt = 0;
    for (int i = 1; i <= n; i++){
        if(i == pos)//i等于这个点的时候直接跳过就行
            continue;
        cnt += abs(v[i] - v[pos]);
    }
    cout << v[pos] << " " << cnt << endl;
    return;
}

I

这个是一道很经典的最大连续子段和问题

不过多进行讲解,浙江大学陈越老师讲的比我好多了,不懂的同学可以直接去看视频

放一个链接🔗在这里

代码如下

void solve()
{
    int n;
    cin >> n;
    vector<int> a(n + 1);
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    int flag = 0;
    for (int i = 1; i <= n; i++)
    {
        if (a[i] < 0)
            continue;
        else
            flag++;
    }
    auto deal = [&](long long n) ->ll//这个是lambda表达式,看不懂的同学把他单独写出去当成一个函数就行
    {
        long long this_sum = 0;
        long long max_sum = 0;
        for (int i = 1; i <= n; i++)
        {
            this_sum += a[i];
            if (this_sum > max_sum)
                max_sum = this_sum;
            if (this_sum < 0)
                this_sum = 0;
        }
        return max_sum;
    };
    if (flag == 0)
    {
        cout << *max_element(a.begin() + 1, a.end());
    }
    else
    {
        long long ans = deal(n);
        cout << ans;
    }
    return;
}

J

很经典的一道二分,方法和之前学校比赛出过的https://hydro.ac/d/sust/p/3709?tid=649411f212259bc4db64bbf6

记忆面包一模一样

面对这种题目,对l的最大长度进行二分求和即可

void solve()
{
    int n, m;
    cin >> n >> m;
    vector<int> a(n + 1);
    ll l = 0, r = 0;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        r = a[i] > r ? a[i] : r;//取右边界的最大值
    }
    r += 1;//加1为了防止答案就是最大值
    //e.g. n = 3,1 1 1,这种情况
    auto deal = [&](ll x) -> bool
    {
        ll sum = 0;
        for (int i = 1; i <= n; i++)
        {
            sum += a[i] / x;//统计每个木头能切成几段
            if (sum >= m)
                return 1;//假如切成的木头已经大于要求的段数了,就去尝试把他切成更大的段
        }
        return 0;
    };
    while (l + 1 < r)
    {
        ll mid = l + (r - l) / 2;
        if (deal(mid))//如果可以,就去判断更大的段是否可以
            l = mid;
        else//不能,就去尝试切成更小的段
            r = mid;
    }
    cout << l;
    return;
}

M

2023年杭州的签到题

简要题意:定义一个先减少后增加的序列为好的序列,给定一个好的序列。求子序列的平均最大值,要求子序列也要是好的。

很简单的贪心思想,求一个子序列的平均最大值,那么元素越大,数目越少就符合。也就是分为三种情况。

1.全选

2.选最小的和上升的

3.选下降的和最小的

对于这三种情况去取最大值就是结果

code

void solve()
{
    double n;
    cin >> n;
    auto v = vector<ll>(n + 1, 1e18);
    auto suml = v;
    auto sumr = v;
    int pos = 0;
    for (int i = 1; i <= n; i++)
        cin >> v[i], pos = v[i] < v[pos] ? i : pos;
    double sum = accumulate(v.begin() + 1, v.end(), 0);
    double minsum = 0, maxsum = 0;
    for (int i = 1; i <= pos + 1; i++)
        minsum += v[i];
    for (int i = pos - 1; i <= n; i++)
        maxsum += v[i];
    double ans = max({1.00 * (minsum / (pos + 1)), 1.00 * (maxsum / (n - pos + 2)), 1.00 * (sum / n)});
    cout << setiosflags(ios::fixed) << setprecision(6);
    cout << ans;
    return;
}

D

序列求环问题,我还在cf上写过一道类似的,但是寻找和问群友都没得出结果。

记忆化搜索和并查集都能够解决这个问题,如果你不会,可以先学习一下这两个算法。

我这里提供并查集+dfs的方法来解决

void solve()
{
    int n;
    cin >> n;
    vector<int> v(n + 1, 0);
    auto fa = v;
    auto cnt = v;//开一个数组记录环的大小
    for (int i = 1; i <= n; i++)
        cin >> v[i];
    for (int i = 0; i <= n; i++)
        fa[i] = i;//初始化并查集
    function<int(int)> my_find = [&](int x)
    {
        return fa[x] == x ? x : fa[x] = my_find(fa[x]);
    };//路径压缩
    auto merge = [&](int x, int y) 
    {
        fa[my_find(x)] = my_find(y); 
    };//合并

    function<void(int)> dfs = [&](int pos) ->void{
        if(my_find(pos) == my_find(v[pos])){//假如这个点已经合并在一起了,也就是之前的循环跑到过这里,就给cnt(这个点的父亲)加一,然后return
            cnt[my_find(pos)]++;
            return;
        }
        else
            cnt[my_find(pos)]++;//其他情况给这个点的父亲加一
       // cerr<< pos << " " << v[pos] << endl;
        merge(pos, v[pos]);/合并这个点的下标和指向的点
        
        dfs(v[pos]);//对指向的点进行搜索
        return;
    };
    for (int i = 1; i <= n; i++)
        dfs(i);//从每一个点都跑一遍搜索
    for (int i = 1; i <= n; i++)
        cout << cnt[my_find(v[i])] << " ";//最后按照顺序输出即可
    // for (auto i : cnt)
    //     cerr << i << " ";
    return;
}
posted @ 2024-03-24 18:06  应许方舟  阅读(43)  评论(0)    收藏  举报