【Week3 作业】A - 选数问题、B - 区间选点、C - 区间覆盖

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

思路做法:

用回溯法解决该问题。
编写函数backtrack,递归调用此函数,直到到达边界或得到有效解则返回即回溯。首先写代码让数组中的数挨个构造可能的解的数组是很容易实现的,这里使用循环,先不放入直接递归下一层,和放入这个数再递归,就实现了这个数在与不在的两种情况,进而所有可能的情况都可以访问到。关键在于为了优化时间复杂度,我们需要进行剪枝。我们只需要K个数,那么数组中的数超过k时就不需要继续递归;若当前要满足的和小于0,说明选出的数已经大于S,不满足解的要求,直接返回;当前访问的数的索引已经超了给定数组范围,这是一个边界,要返回。若找到了合适的解,记录下来,直接返回。
整个backtrack结束后,记录下来的ans值即为答案。

总结:

很基础的回溯题,注意递归函数怎么写,和如何有效地剪枝。

代码:

#include <list>
#include <stdio.h>
using namespace std;
#define N 16
#define rep(i,s,t) for(int i=s;i<=t;i++)

list<int> nums;
int num[N],ans;

void backtrack(int start,int n,int k,int s){
	if(nums.size()==k&&s==0){
		ans++;
		return;
	}else if(nums.size()>k||s<0||start>=n){
		return;
	}else{
		rep(i,start,n-1){
			nums.push_back(num[i]);
			backtrack(i+1,n,k,s-num[i]);
			nums.pop_back();
		}
	}
}

int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		int n,k,s;
		while(!nums.empty()) nums.pop_back();
		ans=0;
		scanf("%d %d %d",&n,&k,&s);
		rep(i,0,n-1){
			scanf("%d",&num[i]);
		}
		backtrack(0,n,k,s);
		printf("%d\n",ans);
	}
	return 0;
}

B - 区间选点

题意:

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

Input

第一行1个整数N(N<=100)
第2~N+1行,每行两个整数a,b(a,b<=100)

Output

一个整数,代表选点的数目

Sample Input1

2
1 5
4 6

Sample Output1

1

Sample Input2

3
1 3
2 5
4 6

Sample Output2

2

思路做法:

用贪心策略解决区间选点问题。
首先是选择一个贪心准则,依据这个准则找出解。这里用的贪心准则是根据区间的右端点从小到大排序。小的在前代表这个区间结束得早,下一个区间结束的晚,看下一个区间的左端点是否位于它前面区间内部,如果在,那么不需要多余的点来覆盖,否则需要多加一个点,位于其右端点,来覆盖此点,然后循环下去。便历完一遍后,点的数量即为解。

总结:

注意贪心准则的选取,想到一个准则,如果不能实现数学上的证明,要找找是否有反例来推翻。这个题目的准则还是较容易的,实现用一个变量end记录现在的右端点,不断更新就行。

代码:

#include <stdio.h>
#include <algorithm>
#define N 100
using namespace std;

struct Interval{
	int a,b;
	Interval(){}
	Interval(int _a,int _b):a(_a),b(_b){}
	bool operator<(const Interval& i){
		return b<i.b;
	}
}interval[N];

int main(){
	int n,a,b;
	scanf("%d",&n);
	for(int i=0;i<n;i++){
		scanf("%d %d",&a,&b);
		interval[i]=Interval(a,b);
	}
	sort(interval,interval+n);
	int ans=1,end=interval[0].b;
	for(int i=1;i<n;i++){
		if(interval[i].a>end){
			ans++;
			end=interval[i].b;
		}
	}
	printf("%d\n",ans);
	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

Sample Input

3 10
1 7
3 6
6 10

Sample Output

2

思路做法:

可以理解为选取最小数量的小区间来覆盖一个大区间,那小区间就要尽可能的长,并且从头到尾连起来可以覆盖,用贪心法来解决。粗略来看,给定一个界限end(初始end定位大区间的开头处),我们要从剩余的区间中选出左端点位于界限左边(若在右边则脱节,不能完全覆盖),且右端点离界限最远的区间(长度越长,相应的区间总数量可能减少),然后更新end为选出的区间的右端点(具体在函数f上实现,返回找到的最合适区间的索引值,找不到合适的返回-1)。循环下去,直到现有的end到达或超过要覆盖的大区间的右端点,或者找不到可行解,返回ans。

总结:

一开始想的是从输入时就把区间进行剪辑,这样再根据左端点和计算得出的区间长度排序再循环,每更新end,同步更新每个小区间的左端点和长度。后来发现这样麻烦也容易出错,索性不再计算区间长度,直接根据区间的右端点与end的距离来比较。

代码:

#include <stdio.h>
#include <algorithm>
#define N 25000
using namespace std;

struct Interval{
	int a,b;
	Interval(){}
	Interval(int _a,int _b):a(_a),b(_b){}
	bool operator<(const Interval& i) const{
		if(a!=i.a) return a<i.a;
		return b>i.b;
	}
}interval[N];

int f(int start,int end,int num){
	int ans=-1,min=-1;
	for(int i=start;i<end;i++){
		if(interval[i].a<=num+1&&interval[i].b>=num+1){
			if(interval[i].b-num-1>min){
				min=interval[i].b-num-1;
				ans=i;
			}
		}else if(interval[i].a>num+1) break;
	}
	return ans;
}

int main(){
	int n,t;
	while(~scanf("%d %d",&n,&t)){
		int cnt=0,a,b;
		for(int i=0;i<n;i++){
			scanf("%d %d",&a,&b);
//			if(a<=1&&b>=1&&b<=t) interval[cnt++]=Interval(1,b);
//			else if(b>t&&a>=1&&a<=t) interval[cnt++]=Interval(a,t);
//			else if(a>=1&&b<=t) interval[cnt++]=Interval(a,b);
//			else if(a<1&&b>t) interval[cnt++]=Interval(1,t);
			if(a<=t&&b>=1) interval[cnt++]=Interval(a,b);
		}
		sort(interval,interval+cnt);
		int ans=0,index=0,end=0;
		while(end<t){
			index=f(index,cnt,end);
			if(index==-1){  //没找到,断了 
				ans=-1;
				break;
			}
			ans++;  //找到,结果+1 
			end=interval[index].b;  //更新end 
		}
		printf("%d\n",ans);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

容嬷嬷当年一枝花

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值