算法模板(6):贪心

区间问题

1.区间选点

给定N个闭区间,请你在数轴上选择尽量少的点,使得每个区间内至少包含一个选出的点。输出选择的点的最小数量

  • 将每个区间按照右端点从小到大排序。
  • 从前往后依次枚举每个区间。如果当前区间中已经包含点,则直接pass。否则选择当前区间右端点。
struct P {
	int l, r;
	bool operator <(const P& rhp)const {
		return r < rhp.r;
	}
}segs[maxn];
void solve() {
	sort(segs, segs + N);
	int ans = 0, last = -2e9;
	for (int i = 0; i < N; i++) {
		if (last < segs[i].l) {
			last = segs[i].r;
			ans++;
		}
	}
	printf("%d\n", ans);
}

参考例题:POJ 1328 Radar Installation

2.最大不相交区间数量

给定N个闭区间,请你在数轴上选择若干区间,使得选中的区间之间互不相交(包括端点)。输出可选取区间的最大数量。

  • 这个解决方法和上面的那个一模一样。
struct P {
	int l, r;
	bool operator <(const P& rhp)const {
		return r < rhp.r;
	}
}segs[maxn];
void solve() {
	sort(segs, segs + N);
	int ans = 0, last = -2e9;
	for (int i = 0; i < N; i++) {
		if (last < segs[i].l) {
			last = segs[i].r;
			ans++;
		}
	}
	printf("%d\n", ans);
}

3.区间分组

给定N个闭区间,请你将这些区间分成若干组,使得每组内部的区间两两之间(包括端点)没有交集,并使得组数尽可能小。

  • 将所有区间按照左端点从小到大排序
  • 从前完后处理每个区间,看看能否放到现在这个组中(L[ i ] > max_r)。如果无法放入,则开新组。若可以的话,就放进去,并更新max_r。
  • 这道题可以不用之前的队列的方法了,有点麻烦。用大雪菜的堆的做法更好一些。
struct P {
	int l, r;
	bool operator <(const P& rhp)const {
		return l < rhp.l;
	}
}segs[maxn];
void solve() {
	sort(segs, segs + N);
	priority_queue<int, vector<int>, greater<int> > que;
	for (int i = 0; i < N; i++) {
		auto p = segs[i];
		if (que.empty() || que.top() >= p.l) que.push(p.r);
		else {
			que.pop();
			que.push(p.r);
		}
	}
	printf("%d\n", que.size());
}

参考例题:POJ - 3190 Stall Reservations

4.区间覆盖

给定N个闭区间以及一个线段区间,请你选择尽量少的区间,将指定线段区间完全覆盖。输出最少区间数。若无法覆盖,输出“-1”

  • 将所有区间左端点从小到大排序。
  • 从前往后依次枚举每个区间,在所有能覆盖 start 的区间内,选择右端点最大的区间。然后将start更新为右端点的最大值。
struct P {
	int l, r;
	bool operator <(const P& rhp)const {
		return l < rhp.l;
	}
}segs[maxn];
void solve() {
	sort(segs, segs + N);
	bool success = false;
	int res = 0;
	for(int i = 0; i < N; i++){
		int j = i, r = -2e9;
		while (j < N && segs[j].l <= st) {
			r = max(r, segs[j].r);
			j++;
		}
		if (r < st) {
			res = -1;
			break;
		}
		res++;
		if (r >= ed) {
			success = true;
			break;
		}
		st = r;
		i = j - 1;
	}
	if (!success) res = -1;
	printf("%d\n", res);
}

参考例题:POJ 2376 Cleaning Shifts

  • 这道题也是很容易错的。和之前的模板有所改动,题意不完全一样。大雪菜的题目,指的是线段完全覆盖,即前后两条线段必须要有交集。比如1 5,5 10。POJ的题意是,每一个T时间段,都有奶牛在工作。你想,这个标号的规则,不是给端点标号(比如坐标轴就是给点标号),而是给区间标号。
  • 第一个需要改动的:segs[j].l <= st + 1
  • 第二个需要改动的:st初值设为0。这个是防止开头>1的情况。当然这么改的更重要的原因是,上面的那个判断条件已经成了segs[i].l <= 2 了,我们只有st初值为0,判断条件才是 segs[j].l <= 1。
int N, st = 0, ed;
struct P {
	int l, r;
	bool operator <(const P& rhp)const {
		return l < rhp.l;
	}
}segs[maxn];
void solve() {
	sort(segs, segs + N);
	bool success = false;
	int res = 0;
	for (int i = 0; i < N; i++) {
		int j = i, r = -2e9;
		while (j < N && segs[j].l <= st + 1) {
			r = max(r, segs[j].r);
			j++;
		}
		if (r < st) {
			res = -1;
			break;
		}
		res++;
		if (r >= ed) {
			success = true;
			break;
		}
		st = r;
		i = j - 1;
	}
	if (!success) res = -1;
	printf("%d\n", res);
}

Huffman树

合并果子

其实就是每次选出最小的两个数合并。用优先队列就可。
省去代码。

不等式的应用

排序不等式(n维向量的内积)

(1)排队打水

  • 给两个n维向量,允许交换每个向量各自的分量,怎么样使两个向量内积最大?怎么样使内积最小? 答案就是高中数学选修4-5学的排序不等式。反序和 <= 乱序和 <= 顺序和。

(2)耍杂技的牛(改编)

一头牛的风险值等于,它头上所有牛的总重量(不包括它自己)减去它的身体强壮程度的值,您的任务是确定奶牛的排序,使得所有奶牛的风险值之和的尽可能地小。

  • 这道题其实会发现,就是W - S,而S是确定的,就只让W最小即可。W就是求出序列前缀和,然后求前缀和之和。这个其实打水是一模一样的,都是转化为向量内积去求解。

绝对值不等式

(1)货仓选址

在一条数轴上有 N 家商店,现在需要在数轴上建立一家货仓,使得货仓到每家商店的距离之和最小。

  • 这个用绝对值不等式的角度去思考,会发现建在中位数的位置是正解。
    这道题按照道理说需要按照N是奇数还是偶数来讨论,但是其实可以统一处理。
  • 若拓展至二维。求曼哈顿距离的话,x与y相互独立,只需要两个分别求一下加起来就行。但是若求欧几里得距离的话,这个涉及到费马点的问题,适用于三个点的情况。有点难。
void solve() {
	sort(a, a + N);
	ll ans = 0;
	for (int i = 0; i < N; i++) {
		ans += abs(a[i] - a[N / 2]);
	}
	printf("%lld\n", ans);
}

推公式

(1)玩杂耍的牛

一头牛的风险值等于,它头上所有牛的总重量(不包括它自己)减去它的身体强壮程度的值,您的任务是确定奶牛的排序,使得所有奶牛的风险值最大值尽可能的小。

  • 贪心策略:按照 wi + si 从小到大排序。
  • 易错,考虑到答案可能是负数,那么ans初始值最好是-2e9,不可以是0。
void solve() {
	sort(a, a + N);
	int sum = 0, ans = -2e9;
	for (int i = 0; i < N; i++) {
		ans = max(ans, sum - a[i].s);
		sum += a[i].w;
	}
	printf("%d\n", ans);
}

你可能感兴趣的:(算法模板,算法)