week3作业题:A - 选数问题 B-区间选点 C-区间覆盖

A - 选数问题

题目描述

Given nn positive numbers, ZJM can select exactly KK of them that sums to SS. Now ZJM wonders how many ways to get it!

输入输出格式及样例

Input
The first line, an integer T<=100T<=100, indicates the number of test cases. For each case, there are two lines. The first line, three integers indicate nn, KK and SS. The second line, nn integers indicate the positive numbers.
Output
For each case, an integer indicate the answer in a independent line.Example
Input
1
10 3 10
1 2 3 4 5 6 7 8 9 10
Output
4

说明

Remember that k<=n<=16k<=n<=16 and all numbers can be stored in 32-bit integer

思路

选数问题要求从给定的一堆数字中选出指定数量的数,他们的和为某一特定值,也就是从N个数中取出n个数的组合问题,对于组合问题我们可以用递归的方法,对于每个数只有两种操作,选与不选,于是我们从第一个数开始,递归一次该数被选的情况,然后再递归一次不选的情况,每次递归中又对下一个数进行同样的操作,从2^N种情况中选出符合条件的即可。
当然,如果单纯地采用这种方式会非常的暴力,时间复杂都也非常高,而通过观察我们发现,在递归选数的最终结果中,有很多种情况可能早就不符合条件结果仍然在递归,比如其和早就超过了给定的和S,或者选的数量超过了n,这种我们在递归的途中就可以排除掉,这样就可以大幅降低时间。
于是有了这样的代码:

void dfs(int* test, int i, int s, int& count, int k, int n, vector<int> p) {//test为所给数字堆,s为所给的和,p用来保存选入的数
	if (p.size() == k && s == 0) {//选数数量达到了指定的数量并且其和等于所给和
		count++;
		return;
	}
	if (i >= n)return;//防止越界
	if (p.size() > k || s < 0)return;//选数过多或者选数和过大
	dfs(test, i + 1, s, count, k, n, p);//不选
	
	p.push_back(test[i]);
	dfs(test, i + 1, s - test[i], count, k, n, p);//选
	p.pop_back();//恢复  防止影响其他情况
}~

上面为了方便判断已经选入的数的和是否达标,我们用所给和S的剩余值作为指标,每选入一个数,S就减去该数。

实验代码

#include <iostream>
#include <vector>
using namespace std;
void dfs(int* test, int i, int s, int& count, int k, int n, vector<int> p) {//test为所给数字堆,s为所给的和,p用来保存选入的数
	if (p.size() == k && s == 0) {//选数数量达到了指定的数量并且其和等于所给和
		count++;
		return;
	}
	if (i >= n)return;//防止越界
	if (p.size() > k || s < 0)return;//选数过多或者选数和过大
	dfs(test, i + 1, s, count, k, n, p);//不选
	
	p.push_back(test[i]);
	dfs(test, i + 1, s - test[i], count, k, n, p);//选
	p.pop_back();//恢复 防止影响其他情况
}
int main() {
	int N; cin >> N;
	while (N > 0) {
		int n, k, s; cin >> n >> k >> s;
		int* test = new int[n];
		for (int i = 0; i < n; i++)
			cin >> test[i];
		int count = 0;
		vector<int> p;
		dfs(test, 0, s, count, k, n, p);
		cout << count << endl;//输出符合条件的种类数
		N--;
	}
	return 0;
}~

B 区间选点

题目描述

数轴上有 n 个闭区间 [a_i, b_i]。取尽量少的点,使得每个区间内都至少有一个点(不同区间内含的点可以是同一个)

输入输出格式及样例

Input
第一行1个整数N(N<=100)
第2~N+1行,每行两个整数a,b(a,b<=100)
Output
一个整数,代表选点的数目
Input
2
1 5
4 6
Output
1
Input
3
1 3
2 5
4 6
Output
2

思路

分析题目要求选出最少的点,所以我们采用贪心算法,而关于选取贪心准则,从左往右,我们选择结束时间最早的区间优先满足,而为了尽可能的让该区间选取的点能够满足更多的区间,应该选择该区间的右端点,因为已知该区间是结束时间最早的,所以在该区间右端点的左边不可能会有一个区间的结束,其他区间的结束点都在该区间右端点的右侧,所以我们应该选择该区间靠右的点保证可以让这个点能够进入其他区间,所以我们选择右端点,这就是贪心准则。
在这里插入图片描述
对于此图,我们先选择结束时间最早的ab区间,选b点,然后再看其他区间,选择最早结束的cd区间的右端点d,然后是gh区间,由于d点已经在gh区间内,所以gh区间已经满足,可以跳过,然后是ij区间的j点,这样选取三个点就达到题目要求了。

实验代码

#include <iostream>
#include <queue>
#include <vector>
#include <algorithm>
using namespace std;
struct v {//区间结构体
	int x; int y;//左右端点
	bool operator<(const v b) {//重载小于运算符,来排序
		if (y == b.y)return x > b.x;
		return y < b.y;//按结束时间早排序 早的靠前
	}
};
bool exist(v a, vector<int> q) {//检查区间是否已经满足要求
	for (int i = 0; i < q.size(); i++) {
		int x = q[i];
		if (x >= a.x && x <= a.y)return true;
	}
	return false;
}
void greedy(queue<v> p,vector<int> &q) {
	while (!p.empty()) {
		v a = p.front(); p.pop();
		if (!exist(a, q))q.push_back(a.y);//如果该区间未达到要求,就把他的右端点加入选取队伍中
	}
}
int main() {
	int n; cin >> n;
	v* test = new v[n];
	for (int i = 0; i < n; i++) {
		int x, y; cin >> x >> y;
		v a; a.x = x; a.y = y;
		test[i] = a;
	}
	sort(test, test + n);
	queue<v> p;
	for (int i = 0; i < n; i++)
		p.push(test[i]);
	vector<int> q;
	greedy(p,q);
	cout << q.size() << endl;
	return 0;
}~

C 区间覆盖

题目描述

数轴上有 n (1<=n<=25000)个闭区间 [ai, bi],选择尽量少的区间覆盖一条指定线段 [1, t](1<=t<=1,000,000)。
覆盖整点,即(1,2)+(3,4)可以覆盖(1,4)。
不可能办到输出-1

输入输出格式及样例

Input
第一行:N和T
第二行至N+1行: 每一行一个闭区间。
Output
选择的区间的数目,不可能办到输出-1
Input
3 10
1 7
3 6
6 10
Output
2

提示

这道题输入数据很多,请用scanf而不是cin

思路

本题同样采用贪心算法,考虑贪心准则,由于我们希望用尽可能少的区间来覆盖题目指定的区间,也就是单位区间在所给区间的占有率增加了,所以选取占有率高的即可,但是也不是越高越好,如果我们选取到占有率最高但区间的左右端点都位于所给区间内部的区间,就会把要求覆盖的区间分割成左右两段而使得问题复杂化。
所以本题我们是从那些左端点在所给区间左端点的左边并且在该区间有部分占有率的区间中选占有率最高的区间,这样每选一个区间,剩下的部分也是连续的一段方便进行下一步选取。
所以贪婪指标是:

  1. 左端点在所给区间左端点左边
  2. 右端点在所给区间左端点右边
  3. 右端点与所给区间左端点的差最大

实验代码

#include <iostream>
#include <stdio.h>
#include <algorithm>
#define N 30000
using namespace std;
struct v {
	int x;
	int y;//左右端点
	bool operator<(const v& b)const {//重载小于运算符
		if (x == b.x)return y > b.y;
		else return x < b.x;//按区间左端点小为一级指标,右端点大为次级指标
	}
};
void greedy(int s, int t, v * p, int& count) {
	int n = 0;
	while (1) {
		if (s > t)break;//所给区间已经全部覆盖
		int max = 0;//用来记录选取区间的右端点
		while (p[n].x <= s) {//左端点小于所给区间左端点
			v a = p[n];
			if (max < a.y && a.y>= s ) {//右端点大于区间左端点
				max = a.y ;//选择右端点最大的
			}
			n++;
		}
		if (max == 0) {//没有符合条件的区间,表示无法覆盖所给区间
			count = -1;
			break;
		}
		else {
			count++;
			s = max +1;//选完一个区间后,继续覆盖所给区间剩余部分
		}
	}
}
int main() {
	int n, t; cin >> n >> t;
	v* p = new v[N];
	for (int i = 0; i < n; i++) 
		scanf("%d %d", &p[i].x,&p[i].y);
	sort(p, p + n);
	int count = 0;
	greedy(1, t, p, count);
	cout << count << endl;

	return 0;
}

小结

本次实验是对课上贪心算法的进一步加深,贪心算法核心就是贪,但要贪也要找准合适的指标。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值