山东大学程序设计思维学习笔记作业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.

简单翻译:有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);
}

总结:这一题的贪心策略相对来说没那么简洁,对应的代码也复杂些。一方面可能是本身如此,另一方面可能是我的策略搞复杂了,等讲解后再看看是否需要修改吧。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值