**
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.
简单翻译:有n个正整数,从中选k个使得他们的和为s,输出方法的个数
输入:第一行一个整数T,代表测试的次数。每个测试中,有两行,第一行有n,k,s,第二行是n个正整数。
Example
Input
1
10 3 10
1 2 3 4 5 6 7 8 9 10
Output
4
思路:重视剪枝的题目,没什么好的方法,对于每个数,有选与不选两种选择,但这个复杂度O(2^n)明显不行,尽管n<16,如果直接干肯定要超时。剪枝有两方面:一个是选的超过了k个,二是选中的和已经超过了s。先对这些判断就能减少很多的不必要操作。
//
// Created by haofeng on 3/4/20.
//递归+剪枝
//
#include <iostream>
#include <list>
using namespace std;
int all_number[16];
int T,N,K,SUM,accept;
void decide(int i,list<int>&set,int sum){
if(set.size()==K){//已经选够了
if(sum==SUM){
accept++;
}
return;
}
if(i>=N)return;;//边界
if(set.size()>K)return;//选的超过了K个
if(sum>SUM)return;//已经选的sum大于目标
//先看选择情况
set.push_back(all_number[i]);
decide(i+1,set,sum+all_number[i]);
set.pop_back();
//再看不选情况
decide(i+1,set,sum);
}
int main(){
cin>>T;
for(int i=0;i<T;i++){
list<int> selected;
cin>>N>>K>>SUM;
for(int j=0;j<N;j++){
cin>>all_number[j];
}
decide(0,selected,0);
cout<<accept<<endl;
accept=0;
}
}
总结:也没啥总结的,递归+剪枝,就是多对边界考虑,这种题也变化挺多的…
B - 区间选点(编译器选GNU G++)
数轴上有 n 个闭区间 [a_i, b_i]。取尽量少的点,使得每个区间内都至少有一个点(不同区间内含的点可以是同一个)
Input
第一行1个整数N(N<=100)
第2~N+1行,每行两个整数a,b(a,b<=100)
Output
一个整数,代表选点的数目
Examples
Input
2
1 5
4 6
Output
1
Input
3
1 3
2 5
4 6
Output
2
思路:贪心策略是先按右端点从小到大排序,然后选择当前区间(如果没有点在该区间内)的右端点。证明将在下面给出。
//
// Created by haofeng on 3/7/20.
//
/*
* 基本思路:(1)将可以被包含的放在包含的之上,即如果上面有了点,下面就不用判断了
* (2)一定有一些区间,有重叠但没有包含关系,需要按照一顺序排序,和区间调度是一样的证明
* (3)还有一些区间,无公共部分,很明显分开做就可以了
* ---------
* -------------
* ------
* 按照从左到右的顺序排列,对各个区间而说,除去(1)中情况,剩下的情况就是(2)(3),我们又指出(3)是单独考虑的,先从比较简单的(2)开始做
* 设最优解每个点Ai,贪心解Bi,因为整数区间,不妨设点都是整数的(可以证明凡是小数点,一定有一整数点与其等效)贪心解取当前区间最右端点
* 前第i-1个点Ai,Bi相同,第i点,Ai<Bi,此时,剩下的区间[Ai,end]的长度大于[Bi,end]
* 对于剩余的区间有三种,包含Ai、Bi,不包含Ai、Bi,包含Bi而不包含Ai的
* 所以对于剩下一个区间,相当于是本问题的子问题,贪心策略的解优于或等于最优解,所以贪心策略即为最优解
* 对于(3)中情况,每个区间都需要一个点,贪心策略可行
* 对于(1)中情况,稍微复杂一些,这涉及到对区间排序更细一点的规则我们先想如下情况
* ---
* --------
* ---------------
* 对于这种情况,很明显只要选最小区间里的点就可以,换句话可以说下面两个区间是不需要的
* 如果去除所有的如第二第三个区间,那么这个问题又回到了最初讨论的简单情况,而解是完全一样的
* 总结出适用于贪心规则的排序方式,先比较右端点,从左到右排,如果右端点一致,那么左端点大的在上面
* over
*/
#include<iostream>
#include <algorithm>
using namespace std;
struct interval{
int left,right;
};
int total=0;
int now_point;
bool compareing(interval& itv1,interval& itv2){
if(itv2.right!=itv1.right){ return itv1.right<itv2.right;}
return itv1.left>itv2.left;
}
int main(){
int N;scanf("%d",&N);
interval* ranges=new interval[N];
for(int i=0;i<N;i++){
scanf("%d %d",&ranges[i].left,&ranges[i].right);
}
std::sort(ranges,ranges+N,compareing);
for(int i=0;i<N;i++){
if(now_point>=ranges[i].left&&now_point<=ranges[i].right){//在区间之中
continue;
} else{
total++;
now_point=ranges[i].right;
}
}
printf("%d\n",total);
}
总结:这与区间调度有相似之处,应学会融会贯通。
**
C - 区间覆盖(不支持C++11)
**
数轴上有 n (1<=n<=25000)个闭区间 [ai, bi],选择尽量少的区间覆盖一条指定线段 [1, t]( 1<=t<=1,000,000)。
覆盖整点,即(1,2)+(3,4)可以覆盖(1,4)。
不可能办到输出-1
输入
第一行:N和T
第二行至N+1行: 每一行一个闭区间。
输出
选择的区间的数目,不可能办到输出-1
样例输入
3 10
1 7
3 6
6 10
样例输出
2
思路:仍然是贪心策略,这次的贪心策略是,先按左端点大小从小到大排序,如果左端点一样,那么右端点大的在前。每次选择左端点在已覆盖区间中,同时右端点最靠右的区间。如果中途发现区间出现中断,则终止。最后判断已覆盖的左右端点,看是否完成覆盖。
//
// Created by haofeng on 3/7/20.
//
/*
* 基本思路:和上一题在很多地方是有相似处的,比如说区间的关系分为三种等
* (1)用尽量少的区间,被包含的区间将被无视,要放在包含它的区间之下
* ----------
* -------
* --
* 如上,第二三个区间是没有必要存在的
* (2)把区间从左到右排序,排序细则暂时不探讨
* (3)如果存在毫无交集的区间,那么覆盖失败(在判断时要注意端点问题)
* 设A[k],B[l]分别是最优解和贪心策略的解(先认为可以覆盖)贪心解取左端点在区间内,且右端点最大的点
* 设Ai,Bi之前区间都相同,第i个出现不同,Ai的右端点<Bi的右端点,即剩下的待覆盖区间,A的大于B的
* 待覆盖区间的覆盖是子问题,设剩下m个区间,其中m-1个是一样的,只有一个有区别,即另一种策略中选择的区间
* 对于子问题,贪心策略不需要有区别的这个区间,因为该区间的范围已经覆盖,而最优解可能需要,所以贪心策略等于或优于最优解,即为最优解
* 现在可以讨论排序问题了,本着被包含在下的原因,左端点小的在前,如一样,则右端点大的在前
*/
#include<iostream>
#include <algorithm>
using namespace std;
struct interval{
int left,right;
};
int total=0;//总的区间数
int now_right=0;//当前已经覆盖的
bool compareing(interval& itv1,interval& itv2){
if(itv2.left!=itv1.left){ return itv1.left<itv2.left;}
return itv1.right>itv2.right;
}
int main(){
int T,n;scanf("%d %d",&n,&T);
interval* ranges=new interval[n];
for(int i=0;i<n;i++){
scanf("%d %d",&ranges[i].left,&ranges[i].right);
}
sort(ranges,ranges+n,compareing);
interval temp;temp.left=0;temp.right=0;
for(int i=0;i<n;i++) {//temp和now_left,now_right要合适时更新
if(ranges[i].left-now_right<=1){
//可以选的情况
if(ranges[i].right>temp.right){
//可以更新temp的情况
temp=ranges[i];
}
}
else{
//不可以选的情况
if(ranges[i].left-temp.right<=1){
//把暂存的temp加入后可以选的情况
now_right=temp.right;
temp=ranges[i];
total++;
}
else{
//加入后也不能选,就是拉了呗
total=-1;
break;
}
}
if(temp.right>=T){
//暂存的temp实现了覆盖
total++;
break;
}
}
if(temp.right<T){total=-1;}
printf("%d",total);
}
总结:这一题的贪心策略相对来说没那么简洁,对应的代码也复杂些。一方面可能是本身如此,另一方面可能是我的策略搞复杂了,等讲解后再看看是否需要修改吧。