挖土机编程 CSP-J 2022 模拟赛 Round 2

闲来无事, 做一套CSP-J普及组的题找找状态(
基本都是贪心和模拟题, 适合热手。

1 探索未知

P8584 探索未知 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
模拟一下分数相加相减就行, 注意负数处理。

一个\(\frac{a}{b}\)分数化为最简分数时, 只需要把分子分母同除以分子与分母的最大公约数即可。
即:\(\frac{a/gcd(a,b)}{b/gcd(a,b)}\)

两个分数之间通分则先求出分母之间的最小公倍数, 然后这个公倍数就是新的分母, 前一个的分子乘上公倍数除以前一个的分母, 后一个的分子乘上公倍数除以后一个的分母, 然后相加减。
即:\(\frac{a}{b} + \frac{c}{d}=\frac{a*\frac{lcm(b,d)}{b} + c* \frac{lcm(b,d)}{d}}{lcm(b,d)}\)

用flag记录当前的数的符号。

代码

#include <iostream>
using namespace std;

long long gcd(long long a, long long b)
{
    if (a < b)
        gcd(b, a);
    return b ? gcd(b, a % b) : a;
}

int main()
{
    long long n, res_a = 0, res_b = 1, flag = 1; // 1代表正数, 0代表负数
    cin >> n;
    while (n--)
    {
        long long a, b, op;
        cin >> a >> b >> op;
        if (op == 1)
        {
            long long t = res_b * b / gcd(res_b, b);
            if (flag)
                res_a = res_a * t / res_b + a * t / b;
            else
                res_a = res_a * t / res_b - a * t / b;
            if (res_a < 0)
                res_a = -res_a, flag = !flag;
            res_b = t;
            t = gcd(res_a, res_b);
            res_a /= t;
            res_b /= t;
        }
        else if (op == 2)
        {
            long long t = res_b * b / gcd(res_b, b);
            if (flag)
                res_a = res_a * t / res_b - a * t / b;
            else
                res_a = res_a * t / res_b + a * t / b;

            if (res_a < 0)
                res_a = -res_a, flag = !flag;
            res_b = t;
            t = gcd(res_a, res_b);
            res_a /= t;
            res_b /= t;
        }
    }
    if (!flag)
        cout << "-";
    cout << res_a;
    if (res_b != 1)
        cout << "/" << res_b;
    return 0;
}

2 球状精灵的传说

P8585 球状精灵的传说 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
乍一看还挺复杂, 不过有一句说,新形成的精灵不再参与拥抱, 这么说的话, 最终答案只有两个方向:

  1. 单个精灵的声望
  2. 任意两个精灵拥抱后的声望

对于单个精灵很好算, 遍历一遍就能求出来最大的那个。

而任意两个精灵则需要些思考:
朴素方法枚举所有两两组合肯定是不过的, 复杂度为 \(O(n^2)\)

朴素方法不行那就只能看看有啥规律没有, 对于这两个精灵, 他们拥抱的规则是:
至少有两个值相同时才能拥抱, 拥抱后的精灵将另外的值相加。
{a,x,y} + {b,x,y} = {a + b, x, y}
又根据声望的定义, 他是以最小的r为基准, 也就是说, 当相加的值不是最小的哪个时, 其实合并后的精灵的声望是没变化的, 这个情况在单个精灵就求过。

那什么时候可以变化呢?当 amin(a,x,y) , bmin(b, x, y)时, 此时合成后的精灵才会比之前两个的声望高, 才是对正解有贡献的有效合成。

这样的话, 我们把输入的 {r1, r2, r3} 重新排序, 让最小的在前面, 后面两个随意。
如何找到当前球最好的合成呢?
需要找到 r2,r3 相同的另一个球, 那么我们把其按 r2,r3 从小到大排序时, 能合成的球肯定相邻。

然后就直接让 ball[i] 与 ball[i - 1] 合成(在他俩r2,r3相同情况下), 然后记录所有合成情况的最大值即可。

但这样有可能其r1向加不是最大值, 可以让其在 r2,r3 相等时, 以 r1 从小到大排序。
这样无论怎么求都是最大的和次大的相加。

代码

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 5e5 + 10;
struct Ball {
    int r1, r2, r3, id;
    bool operator < (const Ball& W) const {
        if (r2 == W.r2)
            return r3 < W.r3;
        return r2 < W.r2;
    }
    int get_p() {
        int minx = min(min(r1, r2), r3);
        return minx * minx  * minx / 4;
    }
}b[N];
int n, m;

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        int a[3];
        cin >> a[0] >> a[1] >> a[2];
        sort(a, a + 3);
        b[i] = { a[0], a[1], a[2], i};
    }

    sort(b + 1, b + 1 + n);

    int one = 0, one_i = 0;
    int sum = 0, sum_i = 0, sum_j = 0;

    for (int i = 1; i <= n; i++)
    {
        if (b[i].r2 == b[i - 1].r2 && b[i].r3 == b[i - 1].r3)
        {
            Ball t = { b[i].r1 + b[i - 1].r1, b[i].r2, b[i].r3, i };
            if (sum < t.get_p())
            {
                sum = t.get_p();
                sum_i = b[i].id, sum_j = b[i - 1].id;
            }
        }
        if (one < b[i].get_p())
        {
            one = b[i].get_p();
            one_i = b[i].id;
        }
    }
    if (one >= sum)
        cout << 0 << "\n" << one_i << "\n" << one << "\n";
    else
        cout << 1 << "\n" << min(sum_i, sum_j) << " " << max(sum_i, sum_j) << "\n" << sum << "\n";

    return 0;
}

3 星环防御工事

P8586 星环防御工事 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
根据天数来思考, 当前天数能拦截的只有上一天剩下的, 和今天的。

因为每天最多拦截k质量, 所以最优的解法就是每天都拦截k的质量, 这样得到的总质量是最大的。又因为上一天的只能今天拦截, 今天的还能明天拦截, 所以肯定先拦截昨天的会更好。

定义 prev 表示昨天能拦截的质量, now 为今天能拦截的质量 :
prev = a[i - 1] > k ? k : a[i - 1] 大于k时最多拦截k个
now = (k - prev) > a[i] ? a[i] : (k - prev)
(k-prev) 代表拦截昨天后还剩下能拦截的质量, 如果当前需要拦截的质量大于能拦截的质量, 则只能拦截一部分。

代码


#include <iostream>
#include <algorithm>
using namespace std;
const int N = 3e5 + 10;
int n, k, m;
int a[N];
int main()
{
    cin >> n >> k;
    for (int i = 1; i <= n; i++)
    {
        int day, cost;
        cin >> day >> cost;
        a[day] += cost;
        m = max(day, m);
    }
    int res = 0;
    for (int i = 1; i <= m + 1; i++)
    {
        int prev = a[i - 1] > k ? k : a[i - 1];
        int now = (k - prev) >= a[i] ? a[i] : (k - prev);
        res += prev + now;
        a[i] -= now;
    }
    cout << res << endl;
    return 0;
}

4 新的家乡

P8587 新的家乡 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
给n个数, 求出这些数能组成数 x 的个数, 且求出最大个数时有几种 x。

其组成规则只能是由一个数加上另一个数:
a + b = x
转换一下:
x - a = b
因为x可以取任意的值, 显然需要用循环来枚举x。我们得到x之后便可以枚举a, 然后直接算出b。找到a,b之后就需要判断数组中是否存在这两个数, 若存在则可以用这个x。

对于柱子数量则可以通过求多少个 a 满足 x - aa 同时存在于数组中。枚举x时如果柱子数量等于之前的最大柱子数量则将ans + 1

那么这样我们就能把答案求出来了, 这里还有一个问题, 如何快速判断 x-aa 是否存在于数组中:
最暴力的方法就是遍历整个数组, 求出来 x - a的个数a的个数, 复杂度为 \(O(n)\)
既然是查找, 可否用二分来求?当然不能, 二分不能求出来有多少个。

看一下数据范围: \(h <= 3e3\), 这么小显然可以通过 数组下标 <-> 数值 的映射来处理, 数组元素的值为该数值出现的次数。即a[i] == i 出现的次数

代码


#include <iostream>
using namespace std;
const int N = 7e3 + 10;
int a[N];

int n, m;
int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        int t;
        cin >> t;
        a[t]++;
        m = max(m, t);
    }
    m *= 2;
    int res = 0, ans = 0;
    for (int i = 1; i <= m; i++)
    {
        int sum = 0;
        for (int j = 1; j <= i / 2; j++)
        {
            if (j + j == i)
                sum += a[j] / 2;
            else
                sum += min(a[j], a[i - j]);
        }
            
        if (sum == 0) continue;
        if (res < sum)
        {
            res = sum;
            ans = 1;
        }
        else if (res == sum)
            ans++;
    }
    cout << res << " " << ans << endl;
    return 0;
}
posted @ 2022-10-30 23:21  EdwinAze  阅读(112)  评论(0编辑  收藏  举报