「[NOIP2012 提高组] 借教室」题解

「[NOIP2012 提高组] 借教室」题解

原题目链接:Link

思路一:完全没有 idea?输出 \(0\),得到 \(5\text{pts}\)。

思路二:\(O(nm)\) 暴力,用 O2 可以得到 \(60\text{pts}\)。

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e6 + 5;
template <typename T> void read(T &x) {x = 0; T f = 1; char tem = getchar(); while (tem < '0' || tem > '9') {if (tem == '-') f = -1; tem = getchar();} while (tem >= '0' && tem <= '9') {x = (x << 1) + (x << 3) + tem - '0'; tem = getchar();} x *= f;}
template <typename T> void write(T x) {if (x < 0) {x = -x; putchar ('-');} if (x > 9) write (x / 10); putchar (x % 10 + '0');}

int n, m;
int r[MAXN], d[MAXN], s[MAXN], t[MAXN];
int main() {
    read(n); read(m);
    for (int i = 1; i <= n; i++) read(r[i]);
    for (int i = 1; i <= m; i++) {
        read(d[i]); read(s[i]); read(t[i]);
    }
    for (int i = 1; i <= m; i++) {
        for (int j = s[i]; j <= t[i]; j++) {
            r[j] -= d[i];
            if (r[j] < 0) return printf("-1\n%d\n", i), 0;
        }
    }
    puts("0");
    return 0;
}

思路三:我们可以发现,结果具有单调性。也就是说,天数越大,就越不容易满足。因此,我们可以二分。这是一个奇怪的二分代码:

#include <bits/stdc++.h>
#include <cstring>
using namespace std;
const int MAXN = 1e6 + 5;
template <typename T> void read(T &x) {x = 0; T f = 1; char tem = getchar(); while (tem < '0' || tem > '9') {if (tem == '-') f = -1; tem = getchar();} while (tem >= '0' && tem <= '9') {x = (x << 1) + (x << 3) + tem - '0'; tem = getchar();} x *= f;}
template <typename T> void write(T x) {if (x < 0) {x = -x; putchar ('-');} if (x > 9) write (x / 10); putchar (x % 10 + '0');}

int n, m;
int r[MAXN], d[MAXN], s[MAXN], t[MAXN];
int tmp[MAXN];

bool check(int x) {
    for (int i = 1; i <= x; i++) {
        for (int j = s[i]; j <= t[i]; j++) {
            r[j] -= d[i];
            if (r[j] < 0) return false;
        }
    }
    return true;
}
int main() {
    read(n); read(m);
    for (int i = 1; i <= n; i++) {read(r[i]); tmp[i] = r[i];}
    for (int i = 1; i <= m; i++) {
        read(d[i]); read(s[i]); read(t[i]);
    }
    int L = 1, R = m, mid;
    while (L < R) {
        if (check(mid = L + R >> 1)) L = mid + 1;
        else R = mid;
        memcpy(r, tmp, sizeof(tmp));
    }
    if (L != m) printf("-1\n%d\n", L);
    else puts("0");
    return 0;
}

时间复杂度是 \(O(nm \log m)\)(猜测)?尽管这样写只能得到 \(40\text{pts}\),但也给我们提供了一个二分框架。接下来一个新的问题是,如何优化 check 函数?对于每个订单 \(j\),相当于把 \(r\) 数组的 \(s_j\) 到 \(t_j\) 全部减去 \(d_j\),这让我们想到差分。只需要差分完后 \(O(n)\) 计算前缀和即可。AC代码如下:

#include <bits/stdc++.h>
#include <cstring>
using namespace std;
const int MAXN = 1e6 + 5;
template <typename T> void read(T &x) {x = 0; T f = 1; char tem = getchar(); while (tem < '0' || tem > '9') {if (tem == '-') f = -1; tem = getchar();} while (tem >= '0' && tem <= '9') {x = (x << 1) + (x << 3) + tem - '0'; tem = getchar();} x *= f;}
template <typename T> void write(T x) {if (x < 0) {x = -x; putchar ('-');} if (x > 9) write (x / 10); putchar (x % 10 + '0');}

int n, m;
int r[MAXN], d[MAXN], s[MAXN], t[MAXN];
int c[MAXN];

bool check(int x) {
    for (int i = 1; i <= x; i++)
        c[s[i]] += d[i], c[t[i] + 1] -= d[i]; // 差分
    for (int i = 1; i <= n; i++) {
        c[i] += c[i - 1]; // 前缀和
        if (c[i] > r[i]) return false;
        // 为什么我们不直接在 r 数组上进行操作呢
        // 因为 check 完后需要将 r 数组还原,这就需要 O(n) 的复杂度
        // 而这样写就不需要还原了
    }
    return true;
}
int main() {
    read(n); read(m);
    for (int i = 1; i <= n; i++) read(r[i]);
    for (int i = 1; i <= m; i++) {
        read(d[i]); read(s[i]); read(t[i]);
    }
    int L = 1, R = m, mid;
    while (L < R) {
        memset(c, 0, sizeof(c));
        if (check(mid = L + R >> 1)) L = mid + 1;
        else R = mid;
    }
    if (L != m) printf("-1\n%d\n", L);
    else puts("0");
    return 0;
}
上一篇:[JS] HTML QQ分享界面js代码


下一篇:洛谷P1080 [NOIP2012 提高组] 国王游戏(贪心,高精度)