经典问题
排队接水问题
问题描述
n n n 个人在一个水龙头前排队接水,假如每个人接水的时间为 T i T_i Ti,找出一个排队顺序,使得 n n n 个人的等待时间之和最少。
思路
每个人的等待时间为前面人的接水时间之和,排队越靠前被计算次数越多,因此越小的排在前面显然是最优的。
选择不相交区间
问题描述
数轴上有 n n n 个开区间 ( l i , r i ) (l_i, r_i) (li,ri),从中选出尽量多的区间,使得选出的这些区间两两没有交点。
思路
若区间相互包含,即区间 x x x 完全包含 y y y。那么选择 x x x 是不划算的,因为 x x x 和 y y y最多只能选择一个,而我们要选择对其他区间影响最小的。
接下来按照右端点 r r r 进行升序排序(之所以不按照左端点 l l l 排序,考虑一下三条线段 [ 1 , 5 ] , [ 2 , 3 ] , [ 4 , 5 ] [1,5],[2,3],[4,5] [1,5],[2,3],[4,5] 的情况)。贪心策略是:一定要选第一个区间,把所有和区间 1 相交的区间排除在外,再选其后第一个可以选择的区间,循环进行。
区间选点问题
问题描述
数轴上有 n n n 个闭区间 [ l i , r i ] [l_i, r_i] [li,ri],取尽量少的点,使得每个区间内都至少有一个点(不同区间内含的点可以是同一个)。
思路
考虑两个区间的情况,如果两个区间有相交区间,那么必定要在这个相交区间内取一点覆盖两个区间。如果三个区间有公共相交区间,同理要在这个相交区间内取一点覆盖三个区间。以此类推,于是我们可以维护一个相交的区间,如果下一条线段和该区间有交点,那么更新该区间为 [ m a x ( l , l i ) , m i n ( r , r i ) ] [max(l, l_i), min(r, r_i)] [max(l,li),min(r,ri)];否则就新开一段区间等于下一条线段。为了使一段区间能覆盖尽可能多的点,同理上面的不相交区间,我们应该对所有区间按右端点 r i r_i ri 降序处理。
实际上没必要维护区间,只需要维护一个右端点就可以,我们发现排序后公共区间的左端点是不断向右收缩的,而如果有相交右端点是不会变的,于是可以只维护右端点。
区间覆盖问题
问题描述
数轴上有 n n n 个闭区间 [ l i , r i ] [l_i, r_i] [li,ri],选择尽量少的区间覆盖一条指定线段 [ s , t ] [s, t] [s,t]。
思路
先进行一次预处理,将每个区间在 [ s , t ] [s, t] [s,t] 外的部分切除。(因为它们的存在是毫无意义的)
在如前所述预处理后,相互包含的区间中,较小的那个显然不应该考虑。把各区间按照左端点
l
l
l 从小到大排序。如果区间 1 的起点不是
s
s
s,无解(因为其他区间的起点更大,不可能覆盖到
s
s
s 点),否则选择起点在
s
s
s 的最长区间。选择此区间
[
l
i
,
r
i
]
[l_i, r_i]
[li,ri] 后,新的起点应该设置为
r
i
r_i
ri,并且忽略所有区间在
r
i
r_i
ri 之前的部分,就像预处理一样。之后选择剩余区间中有效长度最长的一个,如图,
s
s
s 为当前有效起点(此前部分已被覆盖),则应该选择区间 2。重复以上过程直到覆盖了指定区间或者无解。
例题解析
题目来自于洛谷官方题单和《算法竞赛进阶指南》。
洛谷 P5019 [NOIP2018 提高组] 铺设道路
题目大意
有一段道路是 n ( 1 ≤ n ≤ 1 e 5 ) n(1 \leq n \leq 1e5) n(1≤n≤1e5) 块首尾相连的区域,第 i i i 块区域下陷的深度为 d i ( 1 ≤ d i ≤ 1 e 4 ) d_i(1 \leq d_i \leq 1e4) di(1≤di≤1e4)。每天可以选择一段区间 [ l , r ] [l,r] [l,r],这段区间内不能含有为 0 的数,使这段区间内所有数减一。最终希望用最少的次数将所有数都减为 0。
解题思路
一种可行的思路是,每次在当前区间内选择一个最小的数然后将所有数减去这个最小数,然后按 0 作为间隔分界分段递归,这个时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)。
但是上述解法并不是最优的。因为每次都是区间减法,所以考虑差分,设差分数组 d i = a i − a i − 1 d_i = a_i - a_{i-1} di=ai−ai−1。显然要将差分数组都减为0,设 s u m 1 = ∑ { d i ∣ d i > 0 } , s u m 2 = ∑ { d i ∣ d i < 0 } sum_1 = \sum \{d_i | d_i > 0\}, sum_2 = \sum \{d_i | d_i < 0\} sum1=∑{di∣di>0},sum2=∑{di∣di<0},最终的答案就是 m a x ( s u m 1 , s u m 2 ) max(sum_1, sum_2) max(sum1,sum2)。
#include <bits/stdc++.h>
using namespace std;
#define ENDL "\n"
typedef long long ll;
typedef pair<int, int> pii;
const int Mod = 1e9 + 7;
const ll INF = 1e18;
const int maxn = 2e5 + 10;
int a[maxn];
int main() {
// freopen("in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n;
cin >> n;
int ans1 = 0, ans2 = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
int x = a[i] - a[i - 1];
if (x > 0) ans1 += x;
else ans2 -= x;
}
cout << max(ans1, ans2) << endl;
return 0;
}
洛谷 P4447 [AHOI2018初中组]分组
题目大意
给出 n ( 1 ≤ n ≤ 1 e 5 ) n(1 \leq n \leq 1e5) n(1≤n≤1e5) 个数 a i ( ∣ a i ∣ ≤ 1 e 9 ) a_i (|a_i| \leq 1e9) ai(∣ai∣≤1e9)。现在要将所有数分成若干组(分组数量不限),每组的值必须是连续的自然数且每个数只能出现一次。找出一个分组方式,使得人数最少的组人数尽可能的多,输出人数最少的组人数的最大值。
解题思路
首先对数组 a a a 排序离散化然后相同的元素丢进桶内。问题复杂的地方就是对于一段连续的自然数,每个自然数都有若干个,如何分组?需要保证人数最少的组尽可能的多的话,我们需要从前面已经分好的组中,找一个人数最小的优先再丢进去一个人。
上述过程启发我们,开 n n n 个优先队列,对于一段连续的自然数,只需要从每个元素离散化后下标的那个优先队列集合中取出当前连接的最小组人数,注意只需要保存组的人数。
#include <bits/stdc++.h>
using namespace std;
#define ENDL "\n"
typedef pair<int, int> pii;
const int inf = 0x3f3f3f3f;
typedef long long ll;
const int Mod = 1e9 + 7;
const int sz = 1e9;
const int maxn = 1e5 + 10;
int m, a[maxn], b[maxn], cnt[maxn];
unordered_map<int, int> mp;
priority_queue<int, vector<int>, greater<int>> q[maxn];
inline int getID(ll x) {
if (!mp.count(x)) mp[x] = ++m;
return mp[x];
}
int main() {
// freopen("in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n;
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
sort(a + 1, a + 1 + n);
for (int i = 1; i <= n; i++) {
int id = getID(a[i]);
b[id] = a[i];
cnt[id]++;
}
int ans = inf;
for (int j = 0; j < cnt[1]; j++) q[1].push(1);
// cout << q[1].size() << endl;
for (int i = 2; i <= m; i++) {
if (b[i] == b[i - 1] + 1) {
while (!q[i - 1].empty() && cnt[i]) {
q[i].push(q[i - 1].top() + 1);
q[i - 1].pop();
cnt[i]--;
}
if (cnt[i])
for (int j = 0; j < cnt[i]; j++) q[i].push(1);
if (q[i - 1].size()) {
ans = min(ans, q[i - 1].top());
}
} else {
if (q[i - 1].size()) {
ans = min(ans, q[i - 1].top());
}
for (int j = 0; j < cnt[i]; j++) q[i].push(1);
}
}
if (q[m].size()) ans = min(ans, q[m].top());
cout << ans << endl;
return 0;
}
AcWing111. 畜栏预定
题目大意
给出 n ( 1 ≤ n ≤ 50000 ) n(1 \leq n \leq 50000) n(1≤n≤50000) 头牛的吃草开始时间和结束时间,问最少要开放多少个畜栏(牛吃草的位置)才能满足所有的牛都能吃草。
解题思路
对于当前考虑的牛来说,如果前面已经开放了若干个畜栏,需要去哪个畜栏下吃草?显然,每个畜栏被使用的结束时间越早越好,如果最早的还不能满足,那么肯定要新开辟一个畜栏,于是使用优先队列维护畜栏的个数。对于牛来说,因为每头牛都需要被考虑,因此贪心的想要按 l l l 为关键字进行排序。
#include <algorithm>
#include <iostream>
#include <queue>
using namespace std;
#define ENDL "\n"
typedef long long ll;
typedef unsigned long long ull;
typedef pair<double, double> pii;
const double eps = 1e-4;
const int Mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const int maxn = 2e5 + 10;
struct node {
int l, r, id;
bool operator<(const node &p) const { return r >= p.r; }
} a[maxn];
bool cmp(node &p, node &q) { return p.l == q.l ? p.r < q.r : p.l < q.l; }
priority_queue<node> q;
int ans[maxn];
int main() {
// freopen("out.txt", "w", stdout);
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i].l >> a[i].r;
a[i].id = i;
}
sort(a + 1, a + 1 + n, cmp);
int cnt = 0;
q.push({a[1].l, a[1].r, ++cnt});
ans[a[1].id] = cnt;
for (int i = 2; i <= n; i++) {
node cur = q.top();
if (cur.r < a[i].l) {
q.pop();
cur.r = a[i].r;
ans[a[i].id] = cur.id;
q.push(cur);
} else {
q.push({a[i].l, a[i].r, ++cnt});
ans[a[i].id] = cnt;
}
}
cout << cnt << ENDL;
for (int i = 1; i <= n; i++) cout << ans[i] << ENDL;
return 0;
}
AcWing112. 雷达设备
题目大意
在 X O Y XOY XOY 轴上方有 n ( 1 ≤ n ≤ 10000 ) n(1 \leq n \leq 10000) n(1≤n≤10000) 个坐标为 x i , y i x_i, y_i xi,yi 的点, 现在只能在 X X X 轴上设置多个监控使得每个点在某个监控覆盖的一个半径为 d d d 的圆内。输出最少的监控个数。
解题思路
问题转化一下,对于每个点计算出在 x x x 轴上的一段距离,这段距离上只需放置一个监控即可监控到该点。于是问题变成了给出若干个区间,选取最少的点使得每个区间都至少有一个点被选取,也就是区间选点问题。
#include <bits/stdc++.h>
using namespace std;
#define ENDL "\n"
typedef long long ll;
typedef unsigned long long ull;
typedef pair<double, double> pii;
const double eps = 1e-4;
const int Mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const int maxn = 2e5 + 10;
struct node {
int x, y;
} b[maxn];
struct seg {
double l, r;
} a[maxn];
bool cmp(seg &p, seg &q) { return p.r < q.r; }
int ans = 0;
seg check(seg p, seg q) {
if (p.r < q.l) {
ans++;
return q;
}
seg ret = {max(p.l, q.l), min(p.r, q.r)};
return ret;
}
int main() {
// freopen("out.txt", "w", stdout);
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n, d;
cin >> n >> d;
bool flag = 0;
for (int i = 1, x, y; i <= n; i++) {
cin >> b[i].x >> b[i].y;
if (b[i].y > d) flag = 1;
}
if (flag) {
cout << -1 << ENDL;
return 0;
}
for (int i = 1, x, y; i <= n; i++) {
double del = sqrt(1.0 * d * d - 1.0 * b[i].y * b[i].y);
a[i].l = 1.0 * b[i].x - del, a[i].r = 1.0 * b[i].x + del;
}
sort(a + 1, a + 1 + n, cmp);
seg cur = {-inf, -inf};
for (int i = 1; i <= n; i++) {
cur = check(cur, a[i]);
}
cout << ans << ENDL;
return 0;
}
洛谷 P1080 [NOIP2012 提高组] 国王游戏
题目大意
1个国王和 n ( 1 ≤ n ≤ 1000 ) n(1 \leq n \leq 1000) n(1≤n≤1000) 个大臣玩游戏,每个人都有两个数 a , b ( 1 ≤ a , b ≤ 10000 ) a,b(1 \leq a, b \leq 10000) a,b(1≤a,b≤10000)。国王排在队伍的最前面,后面的每个大臣 i i i 的权值为 ∏ j = 0 i − 1 a j \prod_{j = 0} ^{i -1}a_j ∏j=0i−1aj 除以 b i b_i bi。问如何给大臣排序使得权值最大的大臣的权值最小。
解题思路
看到是给出一个排列使得满足 “最大的最小” 型问题,这样的问题一般是邻项交换型贪心。
设两个大臣 ( a i , b i ) , ( a i + 1 , b i + 1 ) (a_i, b_i), (a_{i+1}, b_{i + 1}) (ai,bi),(ai+1,bi+1),第一个大臣前面的乘积为 x x x,假设第一个大臣排在前面,那么二者的权值分别是 x b i , x ∗ a i b i + 1 \frac{x}{b_i}, \frac{x * a_i}{b_{i +1}} bix,bi+1x∗ai;如果交换这两名大臣的位置,那么可以得到二者的权值分别变为 x b i + 1 , x ∗ a i + 1 b i \frac{x}{b_{i+1}}, \frac{x * a_{i+1}}{b_{i}} bi+1x,bix∗ai+1。
考虑是否交换两个大臣的位置,就是比较 m a x { x b i , x ∗ a i b i + 1 } max\{ \frac{x}{b_i}, \frac{x * a_i}{b_{i +1}}\} max{bix,bi+1x∗ai} 与 m a x { x b i + 1 , x ∗ a i + 1 b i } max\{\frac{x}{b_{i+1}}, \frac{x * a_{i+1}}{b_{i}} \} max{bi+1x,bix∗ai+1} 的大小,即交换后的最大值是否会更大。又因为 x b i < x ∗ a i + 1 b i \frac{x}{b_i} < \frac{x * a_{i+1}}{b_{i}} bix<bix∗ai+1 且 x b i + 1 < x ∗ a i b i + 1 \frac{x}{b_{i+1}} < \frac{x * a_i}{b_{i +1}} bi+1x<bi+1x∗ai,实际上就是比较 x ∗ a i b i + 1 ( < , > , = ) x ∗ a i + 1 b i \frac{x * a_i}{b_{i +1}} ~(<,>,=)~ \frac{x * a_{i+1}}{b_{i}} bi+1x∗ai (<,>,=) bix∗ai+1,然后又转化为 a i ∗ b i ( < , > , = ) a i + 1 ∗ b i + 1 a_{i} * b_{i} ~(<,>,=)~ a_{i+1} * b_{i+1} ai∗bi (<,>,=) ai+1∗bi+1。显然需要把 a i ∗ b i a_i * b_i ai∗bi 更小的放在前面。
#include <bits/stdc++.h>
using namespace std;
#define ENDL "\n"
typedef long long ll;
typedef pair<int, int> pii;
const int Mod = 1e9 + 7;
const ll INF = 1e18;
const int maxn = 2e6 + 10;
int n;
struct node {
int a, b;
} t[maxn];
bool cmp(node &p, node &q) {
return p.a * p.b < q.a * q.b;
}
struct BigInt {
int a[8005];
int len;
BigInt() {
memset(a, 0, sizeof a);
len = 0;
}
BigInt(int x) {
if (x == 0) a[0] = 0, len = 1;
else {
len = 0;
while (x) {
a[len++] = x % 10;
x /= 10;
}
}
}
BigInt operator*(const BigInt &T) const {
BigInt ans;
for (int i = 0; i < len; i++) {
for (int j = 0; j < T.len; j++) {
ans.a[i + j] += a[i] * T.a[j] % 10;
ans.a[i + j + 1] += a[i] * T.a[j] / 10;
}
}
for (int i = 0; i < len + T.len; i++) {
ans.a[i + 1] += ans.a[i] / 10;
ans.a[i] %= 10;
}
ans.len = len + T.len;
while (ans.a[ans.len - 1] == 0 && ans.len > 1) ans.len--;
return ans;
}
BigInt operator/(const int &x) const {
BigInt ans;
int pre = 0;
for (int i = len - 1; i >= 0; i--) {
ans.a[i] = (a[i] + pre * 10) / x;
pre = a[i] + pre * 10 - ans.a[i] * x;
}
ans.len = len;
while (ans.a[ans.len - 1] == 0 && ans.len > 1) ans.len--;
return ans;
}
bool operator<(const BigInt &T) const {
if (len > T.len) return false;
if (len < T.len) return true;
for (int i = len - 1; i >= 0; i--) {
if (a[i] < T.a[i]) return true;
else if (a[i] > T.a[i]) return false;
}
return false;
}
BigInt &operator=(const BigInt &T) {
len = T.len;
for (int i = 0; i < len; i++) a[i] = T.a[i];
return *this;
}
void print() {
for (int i = len - 1; i >= 0; i--) cout << a[i];
cout << ENDL;
}
};
int main() {
// freopen("in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n;
cin >> t[0].a >> t[0].b;
for (int i = 1; i <= n; i++) cin >> t[i].a >> t[i].b;
sort(t + 1, t + n + 1, cmp);
BigInt res(t[0].a);
BigInt ans(0);
for (int i = 1; i <= n; i++) {
BigInt cur = res;
cur = cur / t[i].b;
if (ans < cur) ans = cur;
res = res * t[i].a;
}
ans.print();
return 0;
}
AcWing115. 给树染色
题目大意
给定一棵有 n ( 1 ≤ n ≤ 1000 ) n(1 \leq n \leq 1000) n(1≤n≤1000) 个节点的树,每个节点都有一个权值 a i a_i ai, 现在要给这棵树染色,染色的规则是:根节点 R R R 可以随时被染色,任何节点要被染色当且仅当它的父节点已经被染色,一次只能对一个节点进行染色。每个节点被染色的花费是 T ∗ a i T*a_i T∗ai,其中 T T T 代表当前是第 T T T 次染色。输出整棵树染色的最小代价。
解题思路
如果没有染色先后顺序的限制,本题就是排队接水的变形,一定是让所有待染色节点中权值最大的节点先去染色。但是有了这个限制,如果我们对每次可以染色的节点中贪心的选择权值最大的,这样是错误的贪心。容易构造的反例是一个权值很小的点下面有一个权值无穷大的节点。
但是上述思考中有一个重要的性质,即树中除了根节点,所有节点中权值最大的节点一定会在他的父节点被染色之后立即染色。设权值最大的点为 b b b,其父节点为 a a a。
然后我们再考虑这对点和其它点的关系,比如点 cc,那么:
- 如果先染 a , b a,b a,b,再染 c c c,分值是 a + 2 b + 3 c a+2b+3c a+2b+3c;
- 如果先染 c c c,再染 a , b a,b a,b,分值是 c + 2 a + 3 b c+2a+3b c+2a+3b;
计算一下两个分值的差:
a
+
2
b
+
3
c
−
(
c
+
2
a
+
3
b
)
=
2
c
−
(
a
+
b
)
a+2b+3c−(c+2a+3b)=2c−(a+b)
a+2b+3c−(c+2a+3b)=2c−(a+b),这个差小于 0,等价于
c
<
a
+
b
2
c< \frac{a+b}{2}
c<2a+b。
所以当且仅当
a
,
b
a,b
a,b 的平均值大于
c
c
c 时,我们应该先染
a
,
b
a,b
a,b,再染
c
c
c。
所以我们在考虑剩余点的染色顺序时,可以将 a , b a,b a,b 两个点当成一个点,其权值是 a , b a,b a,b 的均值。
进一步推广,如果有两组点: a 1 , a 2 , … a n a_1,a_2,…a_n a1,a2,…an 和 b 1 , b 2 , … b m b_1,b_2,…b_m b1,b2,…bm,组内的点在染色时是相邻的一段。我们现在来考虑何时应该先染第一组点:
- 如果先染 a i a_i ai,则分值是 S a b = ∑ i = 1 n a i ∗ i + ∑ i = n + 1 n + m b i ∗ i S_{ab}=\sum_{i=1}^na_i∗i+\sum_{i=n+1}^{n+m}b_i∗i Sab=∑i=1nai∗i+∑i=n+1n+mbi∗i;
- 如果先染 b i b_i bi,则分值是 S b a = ∑ i = 1 m b i ∗ i + ∑ i = m + 1 n + m a i ∗ i S_{ba}=\sum_{i=1}^mb_i∗i+\sum_{i=m+1}^{n+m}a_i∗i Sba=∑i=1mbi∗i+∑i=m+1n+mai∗i;
则 S a b − S b a = n ∗ ∑ i = 1 m b i − m ∗ ∑ i = 1 n a i S_{ab}−S_{ba}=n∗\sum_{i=1}^mb_i−m∗\sum_{i=1}^na_i Sab−Sba=n∗∑i=1mbi−m∗∑i=1nai,所以 S a b − S b a < 0 ⟺ ∑ i = 1 n a i n < ∑ i = 1 m b i m S_{ab}−S_{ba}<0⟺ \frac{\sum_{i=1}^n a_i}{n} < \frac{\sum_{i=1}^m b_i}{m} Sab−Sba<0⟺n∑i=1nai<m∑i=1mbi。
所以我们在考虑剩余点的染色顺序时,可以将这两组点分别当成两个点,其权值分别是两组内所有点权值的平均值。
上述参考了y总的证明。
总的做法是,考虑除了根节点以外的所有点集,找到 v a l i c n t i \frac{val_i}{cnt_i} cntivali 最大的集合,然后将其暴力合并到父节点的集合后,更新父节点的权值,注意还要将该节点的所有子节点连接到父节点下。最后一定只剩一个根节点,枚举根节点遍历即可求得答案。
#include <bits/stdc++.h>
using namespace std;
#define ENDL "\n"
typedef long long ll;
typedef unsigned long long ull;
typedef pair<double, double> pii;
const double eps = 1e-4;
const int Mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const int maxn = 1005;
int f[maxn];
int val[maxn], v[maxn];
vector<int> g[maxn];
int main() {
// freopen("out.txt", "w", stdout);
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n, root;
cin >> n >> root;
for (int i = 1; i <= n; i++) {
cin >> val[i];
v[i] = val[i];
g[i].push_back(i);
}
for (int i = 1, u, v; i < n; i++) {
cin >> u >> v;
f[v] = u;
}
while (1) {
int cnt = 0;
for (int i = 1; i <= n; i++)
if (g[i].size()) cnt++;
if (cnt == 1) break;
int x = 1, a = inf, idx;
for (int i = 1; i <= n; i++) {
if (i == root || !g[i].size()) continue;
if (1LL * val[i] * a > 1LL * x * g[i].size()) {
x = val[i];
a = g[i].size();
idx = i;
}
}
for (auto i : g[idx]) {
g[f[idx]].push_back(i);
}
for (int i = 1; i <= n; i++)
if (f[i] == idx) f[i] = f[idx];
val[f[idx]] += val[idx];
g[idx].clear();
}
ll ans = 0;
int T = 0;
for (auto i : g[root]) {
++T;
ans += T * v[i];
}
cout << ans << endl;
return 0;
}
AcWing127. 任务
题目大意
有 n ( 1 ≤ n ≤ 1 e 5 ) n( 1 \leq n \leq 1e5) n(1≤n≤1e5) 台机器,每台机器都有一个级别和最大工作时间。还有 m ( 1 ≤ m ≤ 1 e 5 ) m(1 \leq m \leq 1e5) m(1≤m≤1e5) 个任务,每个任务都有级别和需要运行时间,该任务只能被级别不低于它的机器完成。一个机器只能完成一个任务,一个任务只能放入一个机器中执行。设 x , y ( 1 ≤ x ≤ 1440 , 0 ≤ y ≤ 100 ) x,y(1 \leq x \leq 1440, 0 \leq y \leq 100) x,y(1≤x≤1440,0≤y≤100) 分别是任务需要运行时间和级别,一个任务被完成的价值是 500 ∗ x + 2 ∗ y 500 * x + 2 * y 500∗x+2∗y。
解题思路
一开始考虑的是先存起来所有任务,然后枚举机器让每个机器去尽可能贪心的去匹配,但是这样想是错误的,具体可以造反例推翻。
观察到相同运行时间的任务,级别导致的价值最多差 200 200 200,而运行时间如果相差 1 就会导致 500 的价值差距。于是我们可以对所有的任务先按 x x x 为第一关键字降序,然后以 y y y 为第二关键字降序。
枚举每一个任务,设其价值和级别分别是 x i , y i x_i, y_i xi,yi,那么实际上就是找到满足 x k ≥ x i , y k ≥ y i x_k \geq x_i, y_k \geq y_i xk≥xi,yk≥yi 的满足 x k ≥ x i x_k \geq x_i xk≥xi 且使 y k y_k yk 最小的机器。为什么取 y i y_i yi 最小的,因为任务已经按 x x x 已经降序处理,这些满足 x k ≥ x i x_k \geq x_i xk≥xi 的机器一定可以满足后面任务的 x x x,但是不一定满足 y y y,因此我们取最小的即可。具体做法是将机器按 y y y 丢进若干个 m u l t i s e t multiset multiset,然后枚举 a k ∈ [ a i , 100 ] a_k \in [a_i, 100] ak∈[ai,100],二分找到符合条件的最小的,一旦找到退出即可。记录其位置然后将其删除。
#include <bits/stdc++.h>
using namespace std;
#define ENDL "\n"
typedef long long ll;
typedef unsigned long long ull;
typedef pair<double, double> pii;
const double eps = 1e-4;
const int Mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const int maxn = 1e5 + 10;
struct node {
int x, y; //工作时间和级别
} a[maxn];
multiset<int> g[105];
bool cmp(node &p, node &q) { return p.x == q.x ? p.y > q.y : p.x > q.x; }
int main() {
// freopen("out.txt", "w", stdout);
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n, m;
cin >> n >> m;
for (int i = 1, x, y; i <= n; i++) {
cin >> x >> y;
g[y].insert(x);
}
for (int i = 1, x, y; i <= m; i++) cin >> a[i].x >> a[i].y;
sort(a + 1, a + 1 + m, cmp);
ll cnt = 0, ans = 0;
for (int i = 1; i <= m; i++) {
int res = inf, idx = -1;
for (int j = a[i].y; j <= 100; j++) {
if (g[j].empty()) continue;
auto pos = g[j].lower_bound(a[i].x);
if (pos != g[j].end()) {
res = *pos;
idx = j;
break;
}
}
if (idx == -1) continue;
ans += 500 * a[i].x + 2 * a[i].y;
cnt++;
auto pos = g[idx].lower_bound(res);
g[idx].erase(pos);
}
cout << cnt << " " << ans << endl;
return 0;
}