2023ICPC山东省赛

I. 三只骰子

签到。

cpp 复制代码
void solve()
{
    cin >> n >> m;
    int a[7] = {0, 1, 0, 0, 4, 0, 0}, b[7] = {0, 0, 2, 3, 0, 5, 6};
    rep(1, i, 6)
    {
        rep(1, j, 6)
        {
            rep(1, k, 6)
            {
                if (a[i] + a[j] + a[k] == n && b[i] + b[j] + b[k] == m)
                {
                    yes;
                    re;
                }
            }
        }
    }
    no;
}

A. 订单

模拟。

cpp 复制代码
void solve()
{
    cin >> n >> k;
    rep(1, i, n)
    {
        cin >> a[i].first >> a[i].second;
    }
    sort(a + 1, a + 1 + n);
    int now = 0;
    rep(1, i, n)
    {
        auto [x, y] = a[i];
        auto [l, r] = a[i - 1];
        now += (x - l) * k;
        if (now < y)
        {
            no;
            re;
        }
        now -= y;
    }
    yes;
}

G. 匹配

经典思路,把 i − j = a i − a j − > a i − i = a j − j i−j=a_i−a_j-> a_i−i=a_j-j i−j=ai−aj−>ai−i=aj−j 。对 a [ i ] − i a[i]-i a[i]−i 处理即可,分组挑选两两之和大于0并累加。

我用了离散,但因为最多只有1e5个数,所以也可以直接开一个map套vector。

cpp 复制代码
void solve()
{
    cin >> n;
    int num = 1;
    map<int, int> id;
    map<int, int> vs;
    sum = 0;
    rep(1, i, n + 100)
    {
        e[i].clear();
    }
    rep(1, i, n)
    {
        cin >> a[i];
        b[i] = a[i] - i;
        if (vs[b[i]] == 0)
        {
            vs[b[i]] = 1;
            id[b[i]] = num;
            num++;
        }
        int idx = id[b[i]];
        e[idx].emplace_back(a[i]);
    }
    /* rep(1, i, num)
    {
        for (auto j : e[i])
        {
            cout << j << ' ';
        }
        cout << "\n";
    } */

    rep(1, i, num)
    {
        sort(e[i].begin(), e[i].end(), greater<>());
        t = e[i].size();
        for (int j = 0; j < t / 2 * 2; j += 2)
        {
            if (e[i][j] + e[i][j + 1] > 0)
            {
                sum += e[i][j] + e[i][j + 1];
            }
        }
    }
    cout << sum;
}
cpp 复制代码
// 不离散 
void solve()
{
    map<int, vector<int>> mp;
    cin >> n;
    sum = 0;
    rep(1, i, n)
    {
        cin >> a[i];
        mp[a[i] - i].emplace_back(a[i]);
    }
    for (auto [x, y] : mp)
    {
        vector<int> v = y;
        sort(v.begin(), v.end(), greater<>());
        t = v.size();
        for (int j = 0; j < t / 2 * 2; j += 2)
        {
            if (v[j] + v[j + 1] > 0)
            {
                sum += v[j] + v[j + 1];
            }
            else
            {
                break;
            }
        }
    }
    cout << sum;
}

D. 负重越野

答案二分。考虑把速度 > mid的队员的多余速度值加给体重,这样就能最大化他能背的最大重量。

如果每个速度 < mid的队员都能被背上则满足二分条件。

cpp 复制代码
void solve()
{
    cin >> n;
    int mx = 0, mi = 1e16;
    rep(1, i, n)
    {
        cin >> a[i].first >> a[i].second;
        mx = max(mx, a[i].first);
        mi = min(mi, a[i].first);
    }
    int l = mi, r = mx;
    auto ck = [&](int x)
    {
        int num = 0;
        vector<int> v1, v2;
        rep(1, i, n)
        {

            if (a[i].first < x)
            {
                v1.emplace_back(a[i].second);
            }
            else
            {
                b[i] = a[i].second + (a[i].first - x);
                v2.emplace_back(b[i]);
            }
        }
        sort(v1.begin(), v1.end(), greater<>());
        sort(v2.begin(), v2.end(), greater<>());
        /*  for (auto i : v1)
         {
             cout << i << " ";
         }
         cout << "\n";
         for (auto i : v2)
         {
             cout << i << ' ';
         } */
        int t1 = v1.size(), t2 = v2.size();
        if (t1 > t2)
        {
            return 0;
        }
        rep(0, i, t1 - 1)
        {
            if (v1[i] > v2[i])
            {
                return 0;
            }
        }
        return 1;
    };
    while (l <= r)
    {
        int mid = (l + r) >> 1;
        if (ck(mid))
        {
            ans = mid;
            l = mid + 1;
        }
        else
        {
            r = mid - 1;
        }
    }
    /* cout << ck(8); */
    cout << ans;
}

L. 谜题:曲尺

构造。必有解,思路很简单,每次用大一圈的正方形去截已知图形,得到的都是L形。

每次需要找出转折点和长度,长度一直递增所以只要找到每次的转折位置就行。

我的思路是按左下,右上,右下,左上的顺序。

cpp 复制代码
void solve()
{
    cin >> n >> x >> y;
    cout << "Yes\n";
    cout << n - 1 << '\n';
    int num = 1;
    int a = min(n - x, y - 1);
    int b = min(x - 1, n - y);
    int c = max(0ll, n + 1 - x - y);
    int d = max(0ll, x + y - 1 - n);
    int x1 = x, y1 = y;
    while (a--) // 左下
    {
        cout << x + 1 << ' ' << y - 1 << ' ' << -num << ' ' << num << '\n';
        num++;
        x++, y--;
    }
    x = x1, y = y1;
    while (b--) // 右上
    {
        cout << x - 1 << ' ' << y + 1 << ' ' << num << ' ' << -num << '\n';
        num++;
        x--, y++;
    }
    while (c--) // 右下
    {
        cout << num + 1 << ' ' << num + 1 << ' ' << -num << ' ' << -num << '\n';
        num++;
    }
    while (d--) // 右下
    {
        cout << n - num << ' ' << n - num << ' ' << num << ' ' << num << '\n';
        num++;
    }
}

不过官方题解的思路更妙:记录当前的正方形上下左右四个边界,每次向外扩充。

比如如果左边和下面有空位,转折点就在左下。

cpp 复制代码
void solve()
{
    cin >> n >> x >> y;
    cout << "Yes\n";
    cout << n - 1 << '\n';
    int u = x, d = x, l = y, r = y;
    rep(1, i, n - 1)
    {
        if (u > 1 && l > 1)
        { // 左上
            u--, l--;
            cout << u << ' ' << l << ' ' << i << ' ' << i << '\n';
        }
        else if (d < n && l > 1)
        { // 左下
            d++, l--;
            cout << d << ' ' << l << ' ' << -i << ' ' << i << '\n';
        }
        else if (u > 1 && r < n)
        { // 右上
            u--, r++;
            cout << u << ' ' << r << ' ' << i << ' ' << -i << '\n';
        }
        else
        {
            d++, r++;
            cout << d << ' ' << r << ' ' << -i << ' ' << -i << '\n';
        }
    }
}

E. 数学问题

先考虑特殊情况,如果n是m的倍数,消耗为0;除此之外,如果k=1,输出-1。

然后我们分析乘法,对n进行操作一包括的范围为 [ n k , n k + ( k − 1 ) ] [nk,nk+(k-1)] [nk,nk+(k−1)] ,再次进行变为 [ n 2 k , n 2 k + k 2 − 1 ] [n^2k,n^2k+k^2-1] [n2k,n2k+k2−1]

推导: [ n k + ( k − 1 ) ] ∗ k + ( k − 1 ) − > n 2 k + k 2 − k + k − 1 − > n 2 k + k 2 − 1 [nk+(k-1)]*k+(k-1)->n^2k+k^2-k+k-1->n^2k+k^2-1 [nk+(k−1)]∗k+(k−1)−>n2k+k2−k+k−1−>n2k+k2−1

显然进行p次,范围长度为 k p k^p kp,如果 k p > = m k^p>=m kp>=m 则必然存在m的倍数,所以乘法次数上界为 l o g k m log_{k}m logkm。

除法上界为 l o g k n log_{k}n logkn,所以复杂度即为 O ( l o g k n × l o g k m ) O(log_{k}n \times log_{k}m) O(logkn×logkm)。

cpp 复制代码
void solve()
{
    cin >> n >> k >> m >> x >> y;
    if (n % m == 0)
    {
        cout << 0;
        re;
    }
    if (k == 1)
    {
        cout << -1;
        re;
    }
    int num = 0;
    ans = 1e18;
    while (1)
    {
        int l = n % m, len = 1;
        for (int i = 0;; i++) // 乘的次数
        {
            int dis = (m - l) % m; // 左端点l与m倍数的距离
            if (len > dis)
            {
                ans = min(ans, num + i * x);
                break;
            }
            l = l * k % m;
            len *= k;
        }
        if (n == 0)
            break;

        n /= k;
        num += y;
    }
    cout << ans;
}

B. 建筑公司

题目很清晰,一开始有一些员工和一些项目,每个项目要有对应种类和数量的员工才能完成,完成后获得对应种类和数量的员工。这种有前置条件并解锁后续内容,可以联想拓扑排序。

显然每次增加任意种类的员工都可能完成某个项目的某个条件,所以我们用map套一个小顶堆,前者记录员工种类,后者记录项目要求和对应id号,每次达标就判断一次对应项目的任要求是否全部完成。

当某个项目的所有任务都完成,就可以解锁对应的奖励---新员工,然后循环即可。

cpp 复制代码
void solve()
{
    map<int, priority_queue<pii, vector<pii>, greater<pii>>> mp; 
    // 每种员工对应---需求人数,工程编号
    map<int, int> now;                                           // 已拥有的
    cin >> n;
    rep(1, i, n) //因为初始加入也要进ck函数,所以后面再加
    {
        cin >> b[i] >> c[i];
    }
    cin >> m;
    rep(1, i, m)
    {
        cin >> a[i]; // 第i个工程还差多少条件未满足
        rep(1, j, a[i])
        { // 需求
            cin >> x >> y;
            mp[x].emplace(y, i); // 第i种员工,需求人数,工程编号
        }
        cin >> t;
        rep(1, j, t)
        { // 奖励
            cin >> x >> y;
            e[i].emplace_back(x, y);
        }
    }
    queue<int> q;
    rep(1, i, m)
    {
        if (a[i] == 0)
        {
            q.push(i);
        }
    }
    ans = 0;
    auto ck = [&](int x, int y)
    {
        now[x] += y;
        auto &pq = mp[x]; // 处理所有和所添加员工有关的工程
        while (pq.size())
        {
            auto [num, id] = pq.top();
            if (now[x] < num)
            {
                break;
            }
            pq.pop();
            if (--a[id] == 0) // 如果某个工程的条件全部满足,发放奖励
            {
                q.push(id);
            }
        }
    };
    rep(1, i, n) 
    {
        ck(b[i], c[i]);
    }
    while (q.size())
    {
        auto t = q.front();
        q.pop();
        ans++;
        for (auto [x, y] : e[t])
        {
            ck(x, y);
        }
    }
    cout << ans;
}
相关推荐
Sakinol#1 小时前
Leetcode Hot 100 ——回溯part01
算法·leetcode
乌萨奇也要立志学C++1 小时前
【Linux】线程池(二)C++ 手写线程池全流程:从核心设计到线程安全、死锁深度解析
linux·c++
feng_you_ying_li1 小时前
list的介绍与底层实现
数据结构·c++·list
星轨初途1 小时前
C++入门基础指南
开发语言·c++·经验分享·redis
罗湖老棍子1 小时前
【例 3】校门外的树(信息学奥赛一本通- P1537)
数据结构·算法·树状数组
醉卧南楼2 小时前
vector在不同场景下的最优声明与数据添加策略
c++·性能优化·vector
guguhaohao2 小时前
平衡二叉树(AVL),咕咕咕!
数据结构·c++·算法
一叶落4382 小时前
LeetCode 137. 只出现一次的数字 II —— 位运算解法
c语言·数据结构·算法·leetcode·哈希算法
阿豪只会阿巴2 小时前
咱这后续安排
c++·人工智能·算法·leetcode·ros2