跳转至

LOJ 10008

LOJ #10008. 「一本通 1.1 练习 4」家庭作业
传送门:LOJ 10008 | SSOIER 1430

题目大意

已知:有 \(n\) 项作业,每个作业如果在规定的时间内交上来的话才有学分。每个作业的截止日期和学分可能是不同的。例如如果一个作业学分为 \(10\),要求在 \(6\) 天内交,那么要想拿到这 \(10\) 学分,就必须在第 \(6\) 天结束前交。
求解:找到一个完成作业的顺序获得最大学分。

解法

本题是贪心算法的典型例题,在此介绍 \(3\) 种方法解决此类问题。

法1:反悔贪心

100 AC 提交记录 | 按照截止时间从小到大排序,如果放不下尝试替换放好的元素中奖励最小的

反悔贪心
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <bits/stdc++.h>
#define lli long long int
using namespace std;
typedef pair<int, int> pii;
const int MAXn = 1e6 + 9;
const int MAXdl = 7e5 + 9;
priority_queue<pii, vector<pii>, greater<pii> > pq;    // first: v; second: time
vector<pii> d;    // first: deadline; second: v

bool cmp(const pii & x, const pii & y) {
    if(x.first == y.first) return x.second > y.second;
    return x.first < y.first;
}

int main() {
    int n;
    scanf("%d", &n);
    for(int i = 1, ia, ib; i <= n; i ++) {
        scanf("%d%d", &ia, &ib);
        d.push_back({ia, ib});
    }
    sort(d.begin(), d.end(), cmp);
    lli ans = 0;
    int t = 1;
    for(pii x : d) {
        if(x.first < t) {
            if(!pq.empty() && pq.top().first < x.second) {
                int tt = pq.top().second;
                ans -= pq.top().first;
                pq.pop();
                pq.push({x.second, tt});
                ans += x.second;
            }
            else continue;
        }
        else {
            pq.push({x.second, t});
            ans += x.second;
            t ++;
        }
    }
    printf("%lld", ans);
    return 0;
}

法2:枚举每个元素

80 TLE 提交记录 | 按照奖励大小排序,优先安排奖励数最大的,且尽量靠后地安排

枚举每个元素
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <bits/stdc++.h>
#define lli long long int
using namespace std;
typedef pair<int, int> pii;
const int MAXn = 1e6 + 9;
bool ved[MAXn];
vector<pii> d;    // first: deadline; second: v

bool cmp(const pii & x, const pii & y) {
    if(x.second == y.second) return x.first > y.first;
    return x.second > y.second;
}

int main() {
    int n;
    scanf("%d", &n);
    for(int i = 1, ia, ib; i <= n; i ++) {
        scanf("%d%d", &ia, &ib);
        d.push_back({ia, ib});
    }
    sort(d.begin(), d.end(), cmp);
    lli ans = 0;
    for(pii x : d) {
        for(int i = x.first; i >= 1; i --)
            if(!ved[i]) {
                ved[i] = true;
                ans += x.second;
                break;
            }
    }
    printf("%lld", ans);
    return 0;
}

使用枚举每个元素的方法并不能通过此题,但对于此类贪心问题是一个可以考虑的选择,如 LOJ 10004. 「一本通 1.1 例 5」智力大冲浪 可以 AC。

LOJ 10004. 「一本通 1.1 例 5」智力大冲浪

传送门:LOJ 10004 | 洛谷 P1230

枚举每个数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> pii;
const int MAXn = 500 + 9;
bool ved[MAXn];
vector<pii> d;    // first: deadline; second: v

bool cmp(const pii & x, const pii & y) {
    if(x.second == y.second) return x.first > y.first;
    return x.second > y.second;
}

int main() {
    int m, n;
    scanf("%d%d", &m, &n);
    for(int i = 0, ia; i < n; i ++) {
        scanf("%d", &ia);
        d.push_back({ia, 0});
    }
    for(int i = 0; i < n; i ++) {
        scanf("%d", &d[i].second);
    }
    sort(d.begin(), d.end(), cmp);
    for(int i = 0; i < n; i ++) {
        int dl = d[i].first;
        bool can = false;
        for(int t = dl; t >= 1; t --)
            if(!ved[t]) {
                ved[t] = true;
                can = true;
                break;
            }
        if(!can) {
            m -= d[i].second;
        }
    }
    printf("%d", m);
    return 0;
}

法3:枚举时间

100 AC 提交记录 | 枚举所有时间,并放入能放入的元素中奖励数最大的。

枚举时间
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <bits/stdc++.h>
#define lli long long int
using namespace std;
const int MAXn = 1e6 + 9;
vector<int> d[MAXn];    // d[x]: 死线为 x 的所有奖励
priority_queue<int> pq;

int main() {
    int n, maxdl = 1;
    scanf("%d", &n);
    for(int i = 1, ia, ib; i <= n; i ++) {
        scanf("%d%d", &ia, &ib);
        d[ia].push_back(ib);
        maxdl = max(maxdl, ia);
    }
    lli ans = 0;
    for(int i = maxdl; i >= 1; i --) {
        for(int v : d[i]) pq.push(v);
        if(!pq.empty()) ans += pq.top(), pq.pop();
    }
    printf("%lld", ans);
    return 0;
}

考虑到本题截止时间的数据范围为 \(1\le t \le 1 \times 10^5\),使用上述代码不会出现 MLE 的情况。如果数据范围较大,可以使用离散化算法,将截止时间数组离散化为一个更小的范围,就可以存放到容器中了。

类似的题目 P2949 [USACO09OPEN] Work Scheduling G 使用离散化算法后 92 MLE 代码:

枚举时间+离散化
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <bits/stdc++.h>
#define lli long long int
using namespace std;
const int MAXn = 1e5 + 9;
vector<int> dl, v, lsh;
vector<int> d[MAXn];
priority_queue<int> pq;
map<int, int> sl, ls;

int main() {
    int n;
    scanf("%d", &n);
    for(int i = 1, ia, ib; i <= n; i ++)
        scanf("%d%d", &ia, &ib),
        dl.push_back(ia),
        v.push_back(ib);
    lsh.assign(dl.begin(), dl.end());
    sort(lsh.begin(), lsh.end());
    int mxdl = lsh.back();
    auto it = unique(lsh.begin(), lsh.end());
    for(int i = 0; i < n; i ++)
        ls[dl[i]] = distance(lsh.begin(), lower_bound(lsh.begin(), it, dl[i])) + 1;
    for(int i = 0; i < n; i ++)
        d[ls[dl[i]]].push_back(v[i]);
    lli ans = 0;
    for(int i = mxdl; i >= 1; i --) {
        if(ls[i]) for(int v : d[ls[i]]) pq.push(v);
        if(!pq.empty()) ans += pq.top(), pq.pop();
    }
    printf("%lld", ans);
    return 0;
}
该题可使用反悔贪心通过

100 AC 提交记录

反悔贪心
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <bits/stdc++.h>
#define lli long long int
using namespace std;
typedef pair<int, int> pii;
priority_queue<pii, vector<pii>, greater<pii> > pque;    // first: value; second: position
vector<pii> d;    // first: endtime; second: value

bool cmpd(const pii & x, const pii & y) {
    if(x.first == y.first) return x.second > y.second;
    return x.first < y.first;
}

int main() {
    int n;
    scanf("%d", &n);
    for(int i = 1, ia, ib; i <= n; i ++) {
        scanf("%d%d", &ia, &ib);
        d.push_back({ia, ib});
    }
    sort(d.begin(), d.end(), cmpd);
    lli ans = 0;
    int p = 1;
    for(pii x : d) {
        if(x.first < p) {
            if(!pque.empty() && x.second > pque.top().first) {
                int pp = pque.top().second;
                ans -= pque.top().first;
                pque.pop();
                pque.push({x.second, pp});
                ans += x.second;
            }
            else continue;
        }
        else {
            pque.push({x.second, p});
            ans += x.second;
            p ++;
        }
    }
    printf("%lld", ans);
    return 0;
}