Week 6

洛谷题解

A 集合写真

题目描述

\(N\)个人从矮到高排列,这时候又来了一个人,请你确定这个人的位置。

思路

考察STL基本函数的运用
直接用用upper_bound或者lower_bound直接查询位置输出就行了。

参考代码

#include <bits/stdc++.h>

using namespace std;
using i64 = long long;

#define int i64
#define len(p) int(p.size())

constexpr int inf = 1E9;

void solve() {
    int n;
    cin >> n;
	
    vector<int> a(n);
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
    int x;
    cin >> x;
    cout << lower_bound(a.begin(), a.end(), x) - a.begin() + 1 << "\n";
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
	
    solve();
	
    return 0;
}

之后只贴出solve函数代码,如果部分代码出现了无法编译的情况,大概率是因为你的C++版本太低。一般情况下我会在下方给出替代方案,如果我忘了,那你可以尝试询问\(AI\),让\(AI\)将参考代码转换为 -std=c++14 可以编译运行的代码。

B 进击的奶牛

题目描述

\(n\) 间牛舍,第 \(i\) 间牛舍在 \(x_i\) 的位置,合理分配 \(m\) 头牛到不同的牛舍,最大化最近的两头牛之间的距离。

思路

二分最近的两头牛之间的距离,然后check函数就是根据当前的距离贪心去分配每一头牛的位置,然后判断能不能全部分配完。

参考代码

void solve() {
    int n, m;
    cin >> n >> m;
	
    vector<int> a(n);
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
    sort(a.begin(), a.end());
	
    auto check = [&](int x) -> bool {
        int cnt = 0;
        int pv = -inf;
        for (int i = 0; i < n; i++) {
            if (a[i] - pv >= x) {
                cnt++;
                pv = a[i];
            }
        }
        return (cnt >= m);
    };
	
    int lo = 0, hi = inf;
    while (lo + 1 < hi) {
        int mid = (lo + hi) / 2;
        if (check(mid)) {
            lo = mid;
        } else {
            hi = mid;
        }
    }
    cout << lo << "\n";
}

C 分巧克力

题目描述

\(N\) 块巧克力,其中第 \(i\) 块是 \(H_i \times W_i\) 的方格组成的长方形。要从这 \(N\) 块巧克力中切出 \(K\) 块巧克力分给小朋友们。切出的巧克力需要满足:

  1. 形状是正方形,边长是整数。
  2. 大小相同。

求每个小朋友分到的巧克力的边长的最大值。

思路

二分判断巧克力边长,计算按照这种方式最多能分多少个巧克力,判断是否大于等于小朋友的总数即可。

参考代码

void solve() {
    int n, k;
    cin >> n >> k;
	
    vector<pair<int, int>> a(n);
    for (auto &[x, y] : a) {
        cin >> x >> y;
    }
	
    auto check = [&](int x) -> bool {
        int cnt = 0;
        for (auto [u, v] : a) {
            cnt += (u / x) * (v / x);
        }
        return cnt >= k;
    };
	
    int lo = 0, hi = inf;
    while (lo + 1 < hi) {
        int mid = (lo + hi) / 2;
        if (check(mid)) {
            lo = mid;
        } else {
            hi = mid;
        }
    }
    cout << lo << "\n";
}

如果下面这两段代码

vector<pair<int, int>> a(n);
for (auto &[x, y] : a) {
    cin >> x >> y;
}
for (auto [u, v] : a) {
    cnt += (u / x) * (v / x);
}

出现了编译错误就用:

vector<pair<int, int>> a(n);
for (int i = 0; i < n; i++) {
	int x, y;
    cin >> x >> y;
    a[i] = {x, y};
}
for (int i = 0; i < n; i++) {
	int u = a[i].first, v = a[i].second;
    cnt += (u / x) * (v / x);
}

其中,第一段代码这种写法,我们称之为结构化绑定。如果你自己电脑中的C++版本比较高,建议学习一下这种写法,不过可惜的是,机房的清朝老电脑最多只能运行到c++14,平时在机房考试的时候只能用后面这种(悲)

D 小杨的幸运数

题目描述

所有大于等于 \(a\) 的完全平方数都是超级幸运数,所有超级幸运数的整数倍称之为幸运数。
给出 \(N\) 个数,首先判断它们是不是幸运数;接着,对于非幸运数,找出第一个比他大的幸运数。

思路

先求出大于\(a\)的完全平方数,然后创建一个桶,对于所有完全平方数,我们去遍历他们的倍数,并将他们全部标记为\(true\)或者\(1\)。复杂度是一个调和级数,为 \(O(n \log n)\)
接着我们将桶中标记为\(true\)的全部\(push\)到一个\(vector\)中,对于每一次查询,如果\(ok[x] = 1\), 则直接输出\(lucky\),否则用\(upper\_bound\)查找第一个大于\(x\)的值。

参考代码

constexpr int N = 1.2E6;
vector<int> ok(N, 0);

void solve() {
    int a, n;
    cin >> a >> n;
	
    int cur = 1;
    vector<int> p;
    while (true) {
        int y = cur * cur;
        if (y < a) {
            cur++;
        } else if (y > N) {
            break;
        } else {
            p.push_back(y);
            cur++;
        }
    }
    for (int x : p) {
        for (int i = 1; i * x < N; i++) {
            ok[i * x] = 1;
        }
    }
	
    vector<int> q;
    for (int i = 0; i < N; i++) {
        if (ok[i]) {
            q.push_back(i);
        }
    }
    for (int i = 0; i < n; i++) {
        int x;
        cin >> x;
        if (ok[x]) {
            cout << "lucky\n";
        } else {
            cout << *upper_bound(q.begin(), q.end(), x) << "\n";
        }
    }
}

这里筛出所有幸运数的方法和下节课谢学长要讲的质数筛有异曲同工之妙。

E 立定跳远

题目描述

\(n\) 个检查点 \(a_1, a_2, \cdots , a_n\)\(a_i \ge a_{i−1} > 0\)。小明必须先后跳跃到每个检查点上且只能跳跃到检查点上。同时,小明可以自行再增加 \(m\) 个检查点让自己跳得更轻松。

在运动会前,小明制定训练计划让自己单次跳跃的最远距离达到 \(L\),并且学会一个爆发技能可以在运动会时使用一次,使用时可以在该次跳跃时的最远距离变为 \(2L\)。小明想知道,\(L\) 的最小值是多少可以完成这个项目?

思路

二分单次跳跃的最远距离 \(L\),去判断增加的检查点数量是否小于等于 \(m + 1\) (关于为什么是\(m + 1\),你可以将释放技能等效成额外增加一个检查点)。

参考代码

void solve() {
    int n, m;
    cin >> n >> m;
	
    vector<int> p;
    int pv = 0;
    for (int i = 0; i < n; i++) {
        int x;
        cin >> x;
        p.push_back(x - pv);
        pv = x;
    }
	
    auto check = [&](int x) -> bool {
        int cnt = 0;
        for (auto y : p) {
            if (y > x) {
                cnt += (y + x - 1) / x - 1;
            }
        }
        return cnt <= m + 1;
    };
	
    int lo = 0, hi = inf;
    while (lo + 1 < hi) {
        int mid = (lo + hi) / 2;
        if (check(mid)) {
            hi = mid;
        } else {
            lo = mid;
        }
    }
    cout << hi << "\n";
}

F 奶牛晒衣服

题目描述

一件衣服在自然条件下一秒可以晒干 \(a\) 点湿度,使用烘衣机一秒可以晒干 \(a+b\) 湿度,但烘干机同一时间只能烘一件衣服。现在有 \(n\) 件衣服,第 \(i\) 衣服的湿度为 \(w_i\),求出弄干所有衣服的最少时间。

思路

二分弄干所有衣服的最少时间 \(t\) 。遍历每一件衣服,如果 \(t\) 时间内不能晒干这件衣服,那么计算该衣服额外需要花多少时间才能用烘干机烘干,其计算公式为:

\[\left\lceil \frac{w[i] - a \cdot t}{b} \right\rceil \]

累计总共需要使用烘干机的时间\(cnt\),小于等于\(t\)则返回 \(true\)

参考代码

void solve() {
    int n, a, b;
    cin >> n >> a >> b;
	
    vector<int> w(n);
    for (int i = 0; i < n; i++) {
        cin >> w[i];
    }
	
    auto check = [&](int t) -> bool {
        int cnt = 0;
        for (int i = 0; i < n; i++) {
            if (a * t < w[i]) {
                cnt += (w[i] - a * t + b - 1) / b;
            }
        }
        return cnt <= t;
    };
	
    int lo = 0, hi = inf;
    while (lo + 1 < hi) {
        int mid = (lo + hi) / 2;
        if (check(mid)) {
            hi = mid;
        } else {
            lo = mid;
        }
    }
    cout << hi << "\n";
}

G 递增三元组

题目描述

给定三个整数数组 \(A = [A_1, A_2,\cdots, A_N]\)\(B = [B_1, B_2,\cdots, B_N]\)\(C = [C_1, C_2,\cdots,C_N]\)

统计有多少个三元组 \((i, j, k)\) 满足:

  1. \(1 \le i, j, k \le N\)
  2. \(A_i < B_j < C_k\)

思路

对于每一个\(b[i]\),计算\(C\)数组中有多少个数比他大,存入\(cnt\)数组中,对cnt数组跑一个后缀和,存入\(suff\)数组中。那么这时,对于每一个\(a[i]\),用\(upper\_bound\)找出\(B\)数组中第一个比他大的元素的位置\(p\),则\(suff[p]\)中存的就是满足条件的三元组的个数,累加求和即可。

参考代码

void solve() {
    int n;
    cin >> n;
	
    vector<int> a(n), b(n), c(n);
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
    for (int i = 0; i < n; i++) {
        cin >> b[i];
    }
    for (int i = 0; i < n; i++) {
        cin >> c[i];
    }
	
    sort(a.begin(), a.end());
    sort(b.begin(), b.end());
    sort(c.begin(), c.end());
	
    vector<int> cnt(n);
    for (int i = 0; i < n; i++) {
        int p = upper_bound(c.begin(), c.end(), b[i]) - c.begin();
        cnt[i] = n - p;
    }
    vector<int> suff(n + 1);
    for (int i = n - 1; i >= 0; i--) {
        suff[i] = suff[i + 1] + cnt[i];
    }
	
    int sum = 0;
    for (int i = 0; i < n; i++) {
        int p = upper_bound(b.begin(), b.end(), a[i]) - b.begin();
        if (p == n) continue;
        sum += suff[p];
    }
    cout << sum << "\n";
}

H 第 K 小的和

题目描述

给定两个序列 \(A,B\),长度分别为 \(n,m\)

序列 \(C\)包含了 \(A,B\) 中的数两两相加的结果 (共有 \(n\times m\) 个数)。问 \(C\) 中第 \(K\) 小的数是多少。

思路

二分第 \(K\) 小的数的值\(x\),然后计算\(C\)中有多少个数小于等于\(x\),判断其和\(k\)的大小即可。
由于\(C\)数组中数很多,所以不能直接枚举每一个数,而是枚举\(A\)中每一个元素,用\(upper\_bound\)计算\(B\)中有多少个数小于等于\(x - a[i]\),累加即可。

参考代码

void solve() {
    int n, m, k;
    cin >> n >> m >> k;
	
    vector<int> a(n), b(m);
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
    for (int i = 0; i < m; i++) {
        cin >> b[i];
    }
    sort(b.begin(), b.end());
	
    auto check = [&](int x) -> bool {
        int cnt = 0;
        for (int i = 0; i < n; i++) {
            int p = upper_bound(b.begin(), b.end(), x - a[i]) - b.begin();
            cnt += p;
        }
        return cnt >= k;
    };
	
    int lo = 0, hi = inf;
    while (lo + 1 < hi) {
        int mid = (lo + hi) / 2;
        if (check(mid)) {
            hi = mid;
        } else {
            lo = mid;
        }
    }
    cout << hi << "\n";
}

I 中位数

Fun Fact

注意,这个不是个二分题,如果有人是用题解第一篇的方法,那么你的代码时间复杂度是错的,过了只能说明样例太水了。正确的解法应该是用两个堆或者是\(set\)来模拟。

题目描述

给定一个长度为 \(N\) 的非负整数序列 \(A\),对于前奇数项求中位数。

思路

创建一个大根堆和一个小根堆,分别储存前\(\frac{1}{2}\)小的元素和后\(\frac{1}{2}\)小的元素。然后去维护这两个堆的大小一样,输出的时候输出大根堆的堆顶元素

参考代码

void solve() {
    int n;
    cin >> n;
	
    priority_queue<int> a;
    priority_queue<int, vector<int>, greater<int>> b;
	
    for (int i = 0; i < n; i++) {
        int x;
        cin >> x;
		
        a.push(x);
        b.push(a.top());
        a.pop();
        if (b.size() > a.size()) {
            a.push(b.top());
            b.pop();
        }
		
        if (i % 2 == 0) {
            cout << a.top() << "\n";
        }
    }
}

周赛题解

题目难度定位

签到:5 7
简单:2 4 8 9 10 12
ez+:1 3
中等:无
难题:11
防AK:6

5 二分边界(签到)

题目描述

给你一个整数数组 \(nums\),和一个整数 \(target\),请在数组中找到元素等于 \(target\) 的区域。

思路

诈骗题,遍历一遍数组,更新\(target\)第一次和最后一次出现的位置即可。

参考代码

void solve() {
    int n, x;
    cin >> n >> x;
	
    vector<int> a(n);
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
	
    int lo = n, hi = -1;
    for (int i = 0; i < n; i++) {
        if (a[i] == x) {
	        lo = std::min(lo, i);
	        hi = std::max(hi, i);
        }
    }
    if (lo == n + 1 && hi == -1) {
        cout << "[]\n";
    } else {
        cout << "[" << lo << ", " << hi << "]\n";
    }
}

7 Help Xiaoming(签到)

题目描述

根据题意输出对应的拼音

参考代码

vector<string> p = {"ling", "yi", "er", "san", "si", "wu", "liu", "qi", "ba", "jiu"};

void solve() {
    int a, b;
    cin >> a >> b;
    cout << p[a] << " " << p[b] << "\n";
}

2 卡片延伸长度(简单)

题目描述

输出最小的\(x\)使下方的式子成立:

\[\frac{1}{2} + \frac{1}{3} + \frac{1}{4} + \dots + \frac{1}{x + 1} \geq n \]

思路

\(x\)越大上述式子的值越大,直接二分即可

参考代码

auto check = [&](int x) -> bool {
    double sum = 0;
    for (int i = 0; i < x; i++) {
        sum += 1.0 / (i + 2);
    }
    return sum >= n;
};

4 二分法求多项式单根(简单)

题目描述

计算给定3阶多项式在指定范围内的根。

\[f(x) = a_{3}x^3 + a_{2}x^2 + a_{1}x + a_{0} = 0 ,x \in [a, b] \]

思路

按照题意模拟即可。

参考代码

constexpr double eps = 1E-9;

void solve() {
    double a, b, c, d;
    cin >> a >> b >> c >> d;
	
    auto calc = [&](double x) -> double {
        return a * x * x * x + b * x * x + c * x + d;
    };
	
    double lo, hi;
    cin >> lo >> hi;
	
    while (hi - lo > std::max(1.0, lo) * eps) {
        double mid = (lo + hi) / 2;
        double f1 = calc(lo), f = calc(mid);
        if (f1 * f <= 0) {
            hi = mid;
        } else {
            lo = mid;
        }
    }
    cout << fixed << setprecision(2);
    cout << hi << "\n";
}

8 跳石头 & 10 进击的奶牛(简单)

进击的奶牛是作业原题,跳石头只是前者换了个题面。

9 第 k 个数(简单)

题目描述

给定一个 \(n * m\) 的方格矩阵,每个方格内都有一个整数元素。其中第 \(i\) 行第 \(j\) 列的方格中的元素为 \(i * j\)(行和列都从 \(1\) 开始编号)。求第 \(k\)小的数。

思路

二分第 \(k\)小的数是\(x\),统计每一行小于等于\(x\)的数有多少个,和\(k\)比较即可。

参考代码

auto check = [&](int x) -> bool {
    int cnt = 0;
    for (int i = 1; i <= n; i++) {
        cnt += x / i;
    }
    return cnt >= k;
};

12 部落冲突

题目描述

这片平原上有 n 个绿洲。每个绿洲的左上角坐标为 (x, y),宽度为 w,高度为 h。保证所有绿洲不会重叠

酋长想要你将这片平原分成两部分,分别归属于部落 A 和 B。如果界线左边的土地归属于部落 A,右边的土地归属于部落 B。划分的要求如下:

  1. 两部分的绿洲面积尽可能接近。
  2. 部落 A 的绿洲面积应该不小于部落 B 的绿洲面积。

求出岛屿的分界线应该在哪里。

思路

二分分界线位置,判断部落\(A\)分到的土地是否大于等于总面积的一半。

参考代码

void solve() {
    int n;
	cin >> n;
	
    vector<array<int, 4>> a(n);
    int sum = 0;
    for (auto &[x, y, w, h] : a) {
        cin >> x >> y >> w >> h;
        sum += w * h;
    }
	
    auto check = [&](int mid) -> bool {
        int area = 0;
        for (auto [x, y, w, h] : a) {
            area += std::max(0LL, std::min(x + w, mid) - x) * h;
        }
        return area >= sum / 2;
    };
	
    // 二分函数略
}

1 二分查找法之过程 (简单+)

思路

无他,按照题意模拟即可。

参考代码

void solve() {
    int n, x;
    cin >> n >> x;
	
    vector<int> a(n);
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
	
    bool f = true;
    int pre = -100;
    for (int i = 0; i < n; i++) {
        if (a[i] <= pre) {
            f = false;
        }
        pre = a[i];
    }
    if (!f) {
        cout << "Invalid Value\n";
        return;
    }
	
    auto print = [](int lo, int hi) -> void {
        cout << "[" << lo << "," << hi << "][" << (lo + hi) / 2 << "]\n"; 
    };
	
    int lo = 0, hi = n - 1;
    while (lo <= hi) {
        int mid = (lo + hi) / 2;
        print(lo, hi);
        if (a[mid] == x) {
            cout << mid << "\n";
            return;
        }
        if (a[mid] < x) {
            lo = mid + 1;
        } else {
            hi = mid - 1;
        }
    }
    cout << "Not Found\n";
}

3 数列分段(简单+)

题目描述

将长度为\(n\)\(a\)数组分成\(m\)个非空子串,求最小的最大子串和。

思路

二分最小的最大子串和\(x\),贪心去给\(a\)数组分段,使得每一段和均小于等于\(x\),判断分出的段数是否小于\(m\)即可。

参考代码

auto check = [&](int x) -> bool {
    int cnt = 0;
    int sum = 0;
    for (int i = 0; i < n; i++) {
        if (a[i] > x) {
            return false;
        }
        if (sum + a[i] < x) {
            sum += a[i];
        } else if (sum + a[i] == x) {
            sum = 0;
            cnt++;
        } else {
            sum = a[i];
            cnt++;
        }
    }
    return cnt + (sum > 0) <= m;
};

11 赶牛入圈

题目描述

\(N\) 朵三叶草,每朵位于整数坐标 \((x, y)\)\(1 \times 1\) 区域内(\(1 \le x, y \le 10000\)),同一位置可重复出现。要建造一个边与坐标轴平行的正方形畜栏,使得完全包含在畜栏内部的区域中的三叶草总数 ≥ C,求最小边长。

思路

数据范围比较水,所以本题用不到离散化,直接暴力\(check\)即可。
首先先想一个问题,栅栏怎么构建最优?

注意到,当栅栏的某一侧,比方说左边界紧贴当前三叶草的位置时,该栅栏可以充分利用\(x\)轴方向的空间,那么这个时候就可以所有\(x\)轴上满足的点放到一个数组里,对这些点按照\(y\)值排个序,双指针跑一个滑动窗口即可得出最优解。

参考代码

struct point {
    int x, y;
};

void solve() {
    int c, n;
    cin >> c >> n;
	
    vector<point> a(n);
    for (auto &[x, y] : a) {
        cin >> x >> y;
    }
	
    auto check = [&](int d) -> bool {
        for (auto [x1, y1] : a) {
            vector<int> cand;
            for (auto [x2, y2] : a) {
                if (x2 >= x1 && x2 - x1 + 1 <= d) {
                    cand.push_back(y2);
                }
            }
            sort(cand.begin(), cand.end());
            for (int l = 0, r = 0; r < len(cand); r++) {
                while (l <= r && cand[r] - cand[l] + 1 > d) {
                    l++;
                }
                if (r - l + 1 >= c) return true;
            }
        }
        return false;
    };
	
    int lo = 0, hi = 10000;
    while (lo + 1 < hi) {
        int mid = (lo + hi) / 2;
        if (check(mid)) {
            hi = mid;
        } else {
            lo = mid;
        }
    }
    cout << hi << "\n";
}

6 Choose

题目描述

给定长度为 \(n\) 的序列 \(a\),需选择\(k\)长度均为 \(L\) 的连续子数组,即 :

\[[l_1,\, l_1+L-1],\quad [l_2,\, l_2+L-1],\quad \dots,\quad [l_k,\, l_k+L-1] \]

对每个子数组计算其极差\(\max - \min\)),记这 \(k\) 个极差中的最小值\(X\)

要求:

  1. 最大化 极差的最小值\(X\) ;
  2. 在所有使 \(X\) 达到最大值的方案中,最小化 \(L\)

输出\(X\)\(L\)

思路

本来这题应该只能算是个Hard的,但是无奈改不了时间限制,导致部分AC代码被卡常了。

注意到:\(L\) 越大,\(X\) 越大。则可以令 \(L = n - k + 1\),计算出这种情况下极差最小值,即为\(X\) 的最大值;而求出 \(X\) 之后,可以二分 \(L\) 的长度,找到满足极差的最小值为 \(X\) 的下界,即为 \(L\) 的最小值。

现在瓶颈在于,如何快速求出\(k\)个数列的极差的最小值?
一种方案是用 \(multiset\) 模拟,每次塞进一个数,当 \(multiset\) 的大小等于 \(L\) 是就删除最先进队的那个元素。极差就等于当前集合的尾元素减去首元素*--st.end() - *st.begin()。很可惜 \(set\) 的常数比较大没能过时间限制;
一种方案是用两个 \(deque\) 模拟,每次赛进一个数字(其下标为\(i\)),\(dq1\) 记录的是比当前元素大的元素的下标,而 \(dq2\) 记录的是比当前元素小的下标。如果两个队列的队首元素对应的下标 \(j\) 小于 \(i - x + 1\) 那么就出队。此时 \(dq1\) 的队首就是当前序列的极大值,而 \(dq2\) 的队首就是当前队列的极小值。

TLE version

#define len(p) int(p.size())

constexpr int inf = 2E9;

void solve() {
    int n, k;
    cin >> n >> k;
	
    vector<int> a(n);
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
	
    int d = inf;
    int len = n - k + 1;
    multiset<int> st;
    for (int i = 0; i < n; i++) {
        st.insert(a[i]);
        if (len(st) == len) {
            int min = *st.begin();
            int max = *--st.end();
            chmin(d, max - min);
            st.erase(st.find(a[i - len + 1]));
        }
    }
	
    auto check = [&](int x) -> bool {
        st.clear();
        int cnt = 0;
        for (int i = 0; i < n; i++) {
            st.insert(a[i]);
            if (len(st) == x) {
                int min = *st.begin();
                int max = *--st.end();
                if (max - min >= d) {
                    cnt++;
                }
                st.erase(st.find(a[i - x + 1]));
            }
        }
        return cnt >= k;
    };
	
    int lo = 0, hi = len;
    while (lo + 1 < hi) {
        int mid = (lo + hi) / 2;
        if (check(mid)) {
            hi = mid;
        } else {
            lo = mid;
        }
    }
    cout << d << " " << hi << "\n";
}

参考代码

constexpr int inf = 2E9;

void solve() {
    int n, k;
    cin >> n >> k;
	
    vector<int> a(n);
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
	
    int d = inf;
    int len = n - k + 1;
    deque<int> dq1, dq2;
	
    for (int i = 0; i < n; i++) {
        while (!dq1.empty() && a[dq1.back()] <= a[i]) dq1.pop_back();
        dq1.push_back(i);
        while (!dq2.empty() && a[dq2.back()] >= a[i]) dq2.pop_back();
        dq2.push_back(i);
		
        if (i >= len - 1) {
            int j = i - len + 1;
            while (dq1.front() < j) dq1.pop_front();
            while (dq2.front() < j) dq2.pop_front();
            int diff = a[dq1.front()] - a[dq2.front()];
            chmin(d, diff);
        }
    }
	
    auto check = [&](int x) -> bool {
        int cnt = 0;
        dq1 = {}, dq2 = {};
		
        for (int i = 0; i < n; i++) {
            while (!dq1.empty() && a[dq1.back()] <= a[i]) dq1.pop_back();
            dq1.push_back(i);
            while (!dq2.empty() && a[dq2.back()] >= a[i]) dq2.pop_back();
            dq2.push_back(i);
			
            if (i >= x - 1) {
                int j = i - x + 1;
                while (dq1.front() < j) dq1.pop_front();
                while (dq2.front() < j) dq2.pop_front();
                int diff = a[dq1.front()] - a[dq2.front()];
                if (diff >= d) {
                    cnt++;
                }
            }
        }
        return cnt >= k;
    };
	
    int lo = 0, hi = len;
    while (lo + 1 < hi) {
        int mid = (lo + hi) / 2;
        if (check(mid)) {
            hi = mid;
        } else {
            lo = mid;
        }
    }
    cout << d << " " << hi << "\n";
}

AtCoder Beginner Contest 434

A: Balloon Trip

思路

直接输出向下取整+1即可

参考代码

void solve() {
    int w, b;
    cin >> w >> b;
	
    cout << (w * 1000) / b + 1 << "\n";
}

B: Bird Watching

题意

给定\(N\)只鸟,每只鸟有对应的类型\(A_{i}\)和大小\(B_{i}\),有\(M\)种不同的类型,求每种类型鸟的平均大小。

思路

应该没有人不会求平均数吧(doge)

参考代码

void solve() {
    int n, m;
    cin >> n >> m;
	
    vector<vector<int>> a(m + 1);
    for (int i = 0; i < n; i++) {
        int x, y;
        cin >> x >> y;
        a[x].push_back(y);
    }
	
    cout << fixed << setprecision(10);
    for (int i = 1; i <= m; i++) {
        int sum = accumulate(a[i].begin(), a[i].end(), 0LL);
        cout << double(sum) / a[i].size() << "\n";
    }
}

C: Flapping Takahashi

题意

高桥(Takahashi)决定用气球在天空中飞行,一开始(t = 0)的时候在海拔为\(H\)的地方,准备飞行\(10^9 s\),每秒可以选择升高或降低\(1m\),高桥所在的海拔高度最低不会低于\(0m\)

给定\(N\)个限定条件,第\(i\)个条件表示为第\(t_{i}s\)高桥所在高度应当要在\([l_{i}, r_{i}]\)之间,问他能玩完成这\(N\)个目标。

思路

创建两个数组\(mn\)\(mx\),分别表示第\(t_{i}s\)高桥到的最低海拔和最高海拔。
记当前应当达到的最低海拔和最高海拔分别为\(lo\)\(hi\),以及从上一个状态到当前还有\(time\)秒,转移方程为:

\[\begin{aligned} \text{mn}[i] &= \max\bigl(\text{lo},\; \text{mn}[i-1] - \text{time}\bigr), \\ \text{mx}[i] &= \min\bigl(\text{hi},\; \text{mx}[i-1] + \text{time}\bigr). \end{aligned} \]

线性DP跑一遍,对第\(i\)个限制条件而言,如果\(mn[i] > hi\)或者是\(mx[i] < lo\)那么这个条件就是实现不了的,全满足则输出“Yes”。

参考代码

void solve() {
    int n, h;
    cin >> n >> h;
	
    vector<array<int, 3>> a(n + 1);
    for (int i = 1; i <= n; i++) {
        int t, lo, hi;
        cin >> t >> lo >> hi;
        a[i] = {t, lo, hi};
    }
    a[0] = {0, h, h};
	
    vector<int> min(n + 1), max(n + 1);
    min[0] = max[0] = h;
    for (int i = 1; i <= n; i++) {
        auto [t, lo, hi] = a[i];
        int time = t - a[i - 1][0];
        min[i] = std::max(lo, min[i - 1] - time);
        max[i] = std::min(hi, max[i - 1] + time);
        if (min[i] > hi || max[i] < lo) {
            cout << "No\n";
            return;
        }
    }
    cout << "Yes\n";
}

D: Clouds

题意

天空是一个 2000 × 2000 的网格。

\(N\) 朵矩形云朵,第 \(i\) 朵云覆盖所有满足  \(U_i \le r \le D_i\)\(L_i \le c \le R_i\)的格子 \((r, c)\)

对每一朵云,如果只移除自己,问:此时未被任何云覆盖的格子数量是多少?

思路

看到数据范围就应当意识到是个\(n^2\)的做法。
用一个二维差分数组\(diff\)来快速读入\(N\)朵云的覆盖范围,对\(diff\)跑一个前缀和得到\(pref\)数组,那么\(pref\)数组代表的就是每个方块一共被多少朵云给覆盖。

\(pref\)数组可以得知哪些方块是只被一朵云给覆盖的,然后再开一个前缀和数组\(single\)记录这些云。那么对于每次询问,我们只需要计算当前云覆盖的区域内有多少格子是只被一朵云覆盖的,而这个值可以从\(single\)数组中\(O(1)\)查询得到,再加上从未被云覆盖的格子个数就是答案。

参考代码

constexpr int N = 2000;

void solve() {
	int n;
    cin >> n;
	
    vector diff(N + 10, vector<int>(N + 10));
    vector<array<int, 4>> a(n);
    for (auto &[u, d, l, r] : a) {
        cin >> u >> d >> l >> r;
        diff[u][l] += 1;
        diff[u][r + 1] -= 1;
        diff[d + 1][l] -= 1;
        diff[d + 1][r + 1] += 1;
    }
	
    vector pref(N + 10, vector<int>(N + 10));
    for (int i = 1; i <= N; i++) {
        for (int j = 1; j <= N; j++) {
            pref[i][j] = diff[i][j] + pref[i - 1][j] + pref[i][j - 1] - pref[i - 1][j - 1];
        }
    }
    vector single(N + 10, vector<int>(N + 10));
    int cnt = 0;
    for (int i = 1; i <= N; i++) {
        for (int j = 1; j <= N; j++) {
            int x = (pref[i][j] == 1) ? 1 : 0;
            cnt += (pref[i][j] > 0);
            single[i][j] = x + single[i - 1][j] + single[i][j - 1] - single[i - 1][j - 1];
        }
    }
	
    int ans = N * N - cnt;
    for (int i = 0; i < n; i++) {
        auto [u, d, l, r] = a[i];
        int x = single[d][r] - single[u - 1][r] - single[d][l - 1] + single[u - 1][l - 1];
        cout << ans + x << '\n';
    }
}
posted @ 2025-12-03 14:57  EcSilvia  阅读(14)  评论(0)    收藏  举报