寒假训练(二)

[一]二分查找

解题思路

套模板即可

代码实现

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+7;
int n,q,a[maxn];
int main(){
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    cin>>q;
    for(int i=1;i<=q;i++){
        int x;
        cin>>x;
        int pos = lower_bound(a+1,a+n+1,x)-a;
        if(a[pos]==x)cout<<"Yes\n";
        else cout<<"No\n";
    }
    return 0;
}

[二]P1102 A-B 数对

解题思路

方法:排序 + 二分查找

  1. 排序

    • 首先将数组 A 排序,方便后续的二分查找操作。
  2. 二分查找

    • 对于每个元素 A[i] ,我们需要找到满足 A[j] = A[i] + C 的元素 A[j] 。
    • 使用二分查找确定满足 A[j] = A[i] + C 的元素的范围:
      • 第一次二分查找找到第一个大于等于 A[i] + C 的位置 w 。
      • 第二次二分查找找到第一个大于 A[i] + C 的位置 q 。
      • 满足条件的元素个数为 q - w 。
  3. 累加结果

    • 对于每个 A[i] ,累加满足条件的数对数量 q - w 到最终结果 t 中。

代码实现

#include<bits/stdc++.h>
using namespace std;

long long n, l, r, mid, a[5000001], t, q, w, c;

int main() {
    cin >> n >> c;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    sort(a + 1, a + n + 1);
    for (int i = 1; i <= n; i++) {
        l = i + 1;
        r = n;
        while (l <= r) {
            mid = (l + r) / 2;
            if (a[i] + c <= a[mid]) {
                r = mid - 1;
            } else {
                l = mid + 1;
            }
        }
        w = l;
        l = i + 1;
        r = n;
        while (l <= r) {
            mid = (l + r) / 2;
            if (a[i] + c < a[mid]) {
                r = mid - 1;
            } else {
                l = mid + 1;
            }
        }
        q = l;

        t += q - w;
    }
    cout << t;
    return 0;
}

[三]P8647 巧克力切割

题目描述

给定 N 块巧克力的高度 hi 和宽度 wi,要求将这些巧克力切割成边长为 x 的正方形小块,使得总块数至少为 k。目标是找到最大的 x

解题思路

方法:二分查找

  1. 问题转化

    • 每块巧克力可以切割出 (hi/x)×(wi/x) 个边长为 x 的正方形小块。
    • 总块数为所有巧克力切割出的小块数之和。
  2. 二分查找

    • 定义 x 的范围为 [1, 100002] 。
    • 使用二分查找确定最大的 x,使得总块数 k
  3. 验证函数

    • 定义一个函数 isValid,用于检查给定的 x 是否满足总块数 k

代码实现

#include<bits/stdc++.h>
using namespace std;

bool isValid(vector<int> h, vector<int> w, int k, int x) {
    int count = 0;
    int n = h.size();
    for (int i = 0; i < n; ++i) {
        count += (h[i] / x) * (w[i] / x);
    }
    return count >= k;
}

int main() {
    int n, k;
    cin >> n >> k;
    vector<int> h(n);
    vector<int> w(n);
    for (int i = 0; i < n; ++i) {
        cin >> h[i] >> w[i];
    }
    int left = 1;
    int right = 100002;
    while (left < right) {
        int mid = left + (right - left + 1) / 2;
        if (isValid(h, w, k, mid)) {
            left = mid;
        } else {
            right = mid - 1;
        }
    }
    cout << left << endl;
    return 0;
}

[四]P8800 最小化数组总和

题目描述

给定两个长度为 N 的数组AB,以及一个整数 M。要求通过以下操作使得数组 A 的总和最小:

  1. 每次操作可以选择数组 A 中的一个元素 A[i],并将其增加 1。
  2. 操作的总次数不能超过 M
  3. 同时,存在一个限制条件:对于每个iA[i]+B[i] 不能超过一个最小值 min1

解题思路

方法:贪心 + 排序

  1. 计算最小值 min1

    • 遍历数组 AB,计算 A[i]+B[i] 的最小值,记为 min1
  2. 排序数组 A

    • 将数组 A 排序,方便后续的贪心操作。
  3. 贪心操作

    • 遍历排序后的数组 A,尽可能均匀地分配操作次数 M给较小的元素。
    • 每次操作增加 A[i] 的值,直到达到 min1 或操作次数用完。

代码实现

#include<bits/stdc++.h>
using namespace std;

long long n, m, an = 0, min1 = 5000000000, ans = 0;
long long a[500000], b;

int main() {
    cin >> n >> m;
    for (int i = 0; i < n; i++) cin >> a[i];
    for (int i = 0; i < n; i++) {
        cin >> b;
        if (a[i] + b < min1) min1 = a[i] + b;
    }
    sort(a, a + n);
    ans = a[0];
    for (int i = 0; i < n; i++) {
        if (i == n - 1 && an < m) {
            ans += (m - an) / n;
            if (ans > min1) ans = min1;
            break;
        }
        if (a[i] != a[i + 1]) {
            long long temp = (i + 1) * (a[i + 1] - a[i]);
            if (an + temp > m) {
                ans += (m - an) / (i + 1);
                if (ans > min1) ans = min1;
                break;
            }
            an += temp;
            ans += a[i + 1] - a[i];
        }
        if (ans > min1) {
            ans = min1;
            break;
        }
    }
    cout << ans;
    return 0;
}

[五]洛谷 P1281 抄书问题

题目描述

给定 m 页书,每页的页数为 page[i],以及 k 个抄写员。要求将这 m 页书分配给 k 个抄写员,使得所有抄写员中最大的工作量最小。同时,需要输出具体的分配方案。

解题思路

方法:动态规划 + 贪心

  1. 动态规划

    • 定义 dp[i][j] 表示前 j 页书分配给 i 个抄写员时的最小最大工作量。
    • 初始化 dp[1][j] 为前 j 页书的总页数。
    • 状态转移方程:
      dp[i][j]=min(dp[i][j],max(dp[i1][l],sum(l+1,j)))
      其中 sum(l+1,j) 表示从第 l+1 页到第 j 页的总页数。
  2. 输出分配方案

    • 使用递归函数 printp,从后往前分配页数,确保每个抄写员的工作量不超过 dp[k][m]

代码实现

#include<bits/stdc++.h>
using namespace std;

int m, k, page[510], dp[510][510];

void printp(int x, int to) {
    if (to == 0) return;
    int sum = 0, t = 1;
    for (int i = to; i > 0; --i) {
        sum += page[i];
        if (sum > x) {
            sum -= page[i];
            t = i + 1;
            break;
        }
    }
    printp(x, t - 1);
    cout << t << " " << to << endl;
}

int main() {
    // 初始化 dp 数组
    for (int i = 0; i < 510; ++i) {
        for (int j = 0; j < 510; ++j) {
            dp[i][j] = 1e9;
        }
    }
    cin >> m >> k;
    dp[1][0] = 0;
    for (int i = 1; i <= m; ++i) {
        cin >> page[i];
        dp[1][i] = dp[1][i - 1] + page[i];
    }
    // 动态规划求解
    for (int i = 2; i <= k; ++i) {
        for (int j = 1; j <= m; ++j) {
            for (int l = 1; l < j; ++l) {
                int temp = max(dp[i - 1][l], dp[1][j] - dp[1][l]);
                if (temp < dp[i][j]) {
                    dp[i][j] = temp;
                }
            }
        }
    }
    // 输出分配方案
    printp(dp[k][m], m);
    return 0;
}

题解:洛谷 P1281 抄书问题

题目描述

给定 n 个点,每个点的高度为 h[i],以及一个时间限制 T。要求找到一段最长的连续区间,使得在这段区间内移动所需的时间不超过 T。移动时间定义为区间内相邻点高度差的总和。

解题思路

方法:滑动窗口

  1. 滑动窗口

    • 使用双指针 ij 来表示当前区间的左右端点。
    • 初始化 $ i = 1 j = 0 sum = 0$。
    • 遍历数组,每次将j 向右移动,直到 sumT 或者 j 超出数组范围。
    • 更新最大区间长度 ans=max(ans,ji+1)
    • i向右移动,并减去 h[i]的值。
  2. 时间复杂度

    • 由于每个元素最多被访问两次(一次被 j 访问,一次被i 访问),时间复杂度为 O(n)

代码实现

#include <bits/stdc++.h>
#define N 100005
using namespace std;
int n, T, h[N], ans;

int main() {
    scanf("%d%d", &n, &T);
    T <<= 1; // 时间限制乘以 2
    for (int i = 1; i < n; ++i) scanf("%d", &h[i]);
    for (int i = 1, j = 0, sum = 0; i < n; ++i) {
        while (j < n && sum < T) sum += h[++j];
        ans = max(ans, j - i + 1);
        sum -= h[i];
    }
    printf("%d\n", ans);
    return 0;
}
posted @   isletfall  阅读(17)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示