本周主要是DFS的学习,剪枝的优化练习及贪心算法的练习。
Week3 作业3道
A - 选数问题
Given n positive numbers, ZJM can select exactly K of them that sums to S. Now ZJM wonders how many ways to get it!
Input
The first line, an integer T<=100, indicates the number of test cases. For each case, there are two lines. The first line, three integers indicate n, K and S. The second line, n integers indicate the positive numbers.
Output
For each case, an integer indicate the answer in a independent line.
Sample Input
1
10 3 10
1 2 3 4 5 6 7 8 9 10
Sample Output
4
Note
Remember that k<=n<=16 and all numbers can be stored in 32-bit integer
分析
注意到k<=n<=16,每个数包括选与不选两种情况,枚举每一个共2^16种,可以用int,来验证其是否符合要求,但是需要进行可行性剪枝操作,及时终止来降低复杂度。共有两种情况需要剪枝:选的数的个数超过了K,选的数的和超过了sum。其中用list记录被选的数,此时需要迭代一次次进行下一次数的操作。
C++
#include<iostream>
#include<list>
using namespace std;
int t, n, k, s, x, c;
void solve(int i, int sum, list<int>&res,int a[]) {
if (res.size() == k && sum == 0) {
c++;
return;
}
if (i >= n)return;//搜索边界
if (res.size() > k || sum < 0)return;//可行性剪枝
solve(i + 1, sum, res, a);//不选,不放进去
res.push_back(a[i]);//放进去
solve(i + 1, sum - a[i], res, a);//选
res.pop_back();//弹出来,进行一个的选与不选操作
}
int main() {
list<int> number;
while (cin >> t) {
for (int i = 0; i < t; i++) {
cin >> n >> k >> s;
c = 0;
int a[16];
for (int j = 0; j < n; j++) {
cin >> a[j];
}
solve(0, s, number, a);//从第1个开始进行枚举
cout << c << endl;
}
}
return 0;
}
B - 区间选点(编译器选GNU G++)
数轴上有 n 个闭区间 [a_i, b_i]。取尽量少的点,使得每个区间内都至少有一个点(不同区间内含的点可以是同一个)
Input
第一行1个整数N(N<=100)
第2~N+1行,每行两个整数a,b(a,b<=100)
Output
一个整数,代表选点的数目
Sample Input 1
2
1 5
4 6
Sample Output 1
1
Sample Input 2
3
1 3
2 5
4 6
Sample Output 2
2
分析
这是贪心算法的一个应用,主要是要确定贪心指标。即重叠区间越大,所取的点的数量越少,可以将区间按b从小到大的顺序排列,当b相同时a从大到小进行排列,找到下一个左右都不重叠的区间时,取的点的数量++,遍历直到最后一个区间分析完毕。
C++
#include<iostream>
#include<algorithm>
using namespace std;
bool compare(pair<int,int>& a1, pair<int,int>& a2) {
return a1.second == a2.second? a1.first > a2.first: a1.second < a2.second; //按b从小到大的顺序排列,当b相同时a从大到小进行排列
}
int main() {
int n;
pair<int, int> itv[100];
while (cin >> n) {
for (int i = 0; i < n; i++) {
cin >> itv[i].first>>itv[i].second;
}
sort(itv, itv + n, compare);
int sum = 1, t = itv[0].second;
for (int i = 0; i < n; i++) {
if (itv[i].first > t) {
sum++;
t = itv[i].second;//更新
}
}
cout << sum << endl;
}
return 0;
}
C - 区间覆盖(不支持C++11) (重点!!!)
描述
数轴上有 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
Sample Input
3 10
1 7
3 6
6 10
Sample Output
2
提示
这道题输入数据很多,请用scanf而不是cin(scanf运行时间会减少,中文注释减少时间也会减少)
分析
这里的贪心指标是从起点开始的最长的一段,开始的起点是1,后来的起点是前一段最长起点的b+1,再遍历找下一个最长区间,思路比较简单。
C
#include<algorithm>
#include<stdio.h>
using namespace std;
bool compare(const pair<int, int> &a, const pair<int, int> &b) {
return a.first < b.first;
}
int main() {
int n, t, maxOfB, begin = 0, count = 0, index;
pair<int, int> ivt[25001];
while (~scanf("%d%d",&n,&t)) {
for (int i = 0; i < n; i++) {
scanf("%d%d",&ivt[i].first ,&ivt[i].second);
}
sort(ivt, ivt + n, compare);
if (ivt[0].first > 1) {
printf("%d\n",-1 ); //起始点大于1,未覆盖
continue;
}
maxOfB = ivt[0].second, index = 0;
for(int i = 0;i<n;i++){
int j = i;
while (ivt[j].first <= begin + 1 && j < n)
{//该层循环中进行满足可以连接上一段的最大长度的查找,注意begin+1
if (ivt[j].second>maxOfB)
{
maxOfB = ivt[j].second;
index = j;//记录这个最大值的下标
}
j++;
}
count++;
begin = ivt[index].second;//更新下一段的起始值大小
if (maxOfB >= t ) break;//覆盖了的话直接退出
}
if (maxOfB < t) {
printf("%d\n", -1);未覆盖
continue;
}
printf("%d\n", count); ;
}
return 0;
}