程序设计思维与实践 Week3 作业(选数问题、区间选点、区间覆盖)

**

A-选数问题

**
题目

给定n个正整数,要求选出K个数,使得选出来的K个数的和为sum,计算有多少种方案。

Input
第一行输入一个整数T,(T<=100),表示测试数据组数。每一组测试数据包含两行。第一行输入三个正整数:n,K,S;第二行为n个正整数。

Output
对于每组测试数据都在单独一行中输出一个正整数,表示方案个数。

Example
input

1
10 3 10
1 2 3 4 5 6 7 8 9 10

output

4

注意
K<=n<=16,且所有整数都可以用32位整型数据表示。

解题思路
利用递归调用,先将n个整数存入数组中,从数组的第一个元素开始递归调用,每一个元素有两个选择:不被选入K个数中,就继续进入下一个递归调用;选入K个数中,就将其插入到链表中,将其从sum总数中减去,继续进行下一层的递归调用。当传入的元素索引大于总数n、链表元素超过K个或sum小于0时,返回上一层函数;若链表中元素个数为K,且sum恰好减为0,则将方案总数加一。递归调用完成,就将所有可能的方案遍历完成。
C++代码

#include<iostream>
#include<list>
using namespace std;
list<int> res;//定义链表,用于判断选数个数 
int result=0;//存放方案个数 
int *a=new int[17];//数组,存放n个整数 
void solve(int i,int sum,int k,int n,list<int> &res)
{//dfs递归调用函数 
 if(res.size()==k&&sum==0)//当选出K个数且其和为sum 
 {
  result++;
  return;
 }
 if(i>=n) return; 
 if(res.size()>k||sum<0) return;
 solve(i+1,sum,k,n,res);//不选,直接进入下一次调用 
 res.push_back(a[i]);
 solve(i+1,sum-a[i],k,n,res);//选择这个数,将其插入链表,再调用 
 res.pop_back();
}
int main()
{
 int t;
 cin>>t;
 for(int i=0;i<t;i++)
 {
  result=0;
  res.clear();//清空链表 
  int n=0,k=0,s=0;
  cin>>n>>k>>s;
  for(int j=0;j<n;j++)//将n个整数存入数组 
  {
   cin>>a[j];
  }
  solve(0,s,k,n,res);//调用递归函数 
  cout<<result<<endl;//输出结果 
  for(int j=0;j<n;j++)//清空数组 
  {
   a[j]=0;
  }
 }
 return 0; 
} 

**

B - 区间选点

**
题目
数轴上有 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

解题思路
定义结构体存放区间的左右端点,将区间先按照右端点从小到大排序,再根据左端点从小到大排序。排序完成后,先选择第一个区间的右端点,判断该点与下一个区间的关系,找到下一个不包含该点的区间,再次选择下一个区间的右端点,以此类推,直到循环判断完所有的区间,得到最终的点数。
C++代码

#include<iostream>
#include<algorithm> 
using namespace std;
struct area
{
 int a,b;//区间的左右端点 
 bool operator<(const area &x) const 
 {//重载小于号,先根据区间的右端点排序,再根据左端点排序 
  if(b!=x.b) return b<x.b;
  else if(a!=x.a) return a<x.a;
 } 
}; 
 
int main()
{
 int n;
 while(cin>>n)
 {
  area *m=new area[n];//定义结构体数组,存放区间 
  int sum=0;
  for(int i=0;i<n;i++)//为数组赋值 
  {
   cin>>m[i].a>>m[i].b;
  }
  sort(m,m+n);//将数组排序 
  for(int i=0;i<n;i++)
  {
   int point=m[i].b;//每次循环选择区间的右端点 
   sum++;//点数加一 
   while(point>=m[i+1].a)//找到不包含point点的下一个区间 
   {
    i++;
   }
  }
  cout<<sum<<endl; 
  delete [] m;
 }
 return 0;
}

**

C - 区间覆盖

**
题目
数轴上有 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

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

解题思路
定义结构体存放区间的左右端点,将n个区间根据左端点从小到大、右端点从大到小的顺序排序。对区间进行预处理,包含线段左端点的区间,将其在线段左侧的部分删去,即更新区间左端点为线段左端点,在更新后的区间中找到区间长度最大的区间,将线段的左端点更新为该区间的右端点加一,即只考虑线段在该区间右侧的部分,继续之前对区间的操作。如果找到的区间包含了线段的右端点,则完成对线段的覆盖,跳出循环,输出结果即可。如果在循环更新过程中,始终没有包含线段左端点或右端点的区间,则不可能办到,输出-1。
C++代码

#include<algorithm>
#include<stdio.h>
using namespace std;
struct area
{
 int a,b;//区间的左右端点 
 bool operator<(const area &x) const 
 {//重载小于号,选根据区间左端点排序 
  if(a!=x.a) return a<x.a;
  else if(b!=x.b) return b>x.b;
 }
};
int main()
{
 int n=0,t=0;
 scanf("%d%d",&n,&t);
 area line;//线段区间 
 area *m=new area[25002];//存放n个区间 
 line.a=1,line.b=t;
 for(int i=0;i<n;i++)
 {
  scanf("%d%d",&m[i].a,&m[i].b);
 }
 sort(m,m+n);//将区间数组排序,先按照左端点从小到大排,再根据右端点从大到小 
 int count=0;
 int maxlenth=0,maxid=0;//记录每次循环找到的当前区间最大长度,和其在数组中的索引 
  while(true)
 {
  maxlenth=0;
  for(int i=0;i<n;i++)//对区间进行预处理,去掉线段左边的区间长度 
  {
   if(m[i].a<=line.a&&m[i].b>=line.a)
   {
    m[i].a=line.a;//更新区间的左端点 
    if(m[i].b-m[i].a>=maxlenth)//找到长度最长的区间 
    {
     maxlenth=m[i].b-m[i].a;
     maxid=i;
    }
   }
   else if(m[i].a>line.a) break;//遇到左端点大于线段左端点的区间,停止预处理 
  }
    if(m[maxid].a!=line.a)//如果没有包含线段左端点的区间,就说明不可能办到 
  {
   printf("-1\n");
   return 0;
  }
    line.a=m[maxid].b+1;//存在这样的区间,就将线段区间的左端点更新为该区间右端点的下一个整数 
  count++;//找到的区间个数加一 
  if(m[maxid].b>=line.b) break;//如果线段右端点包含在该区间内,则可以停止寻找 
    if(maxid==n-1)//如果遍历完所有区间都没有包含线段右端点的区间,则说明不可能找到 
  {
   printf("-1\n");
   return 0;
  }
 }
  printf("%d\n",count);
 return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值