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.
Examples
Input
1
10 3 10
1 2 3 4 5 6 7 8 9 10
Output
4
解题思路
通过DFS实现,但DFS的特性可能会导致时间复杂度过高或者无解的情况,所以我们可以通过设置条件对搜索过程进行剪枝:
(1)选的数的个数超过了K
(2)选的数的和超过了sum
实现代码
#include<iostream>
#include<cstdio>
using namespace std;
int a[105];
int t,n,k,s;
int res=0;
void dfs(int i,int sum,int count)
{
if(sum>s||count>k) return;//剪枝
if(count==k)
{
if(sum==s)
{
res++;
return;
}
return;
}
if(count<k)
{
for(int m=i;m<n;m++)
dfs(m+1,sum+a[m],count+1);
}
}
int main()
{
cin>>t;
for(int i=0;i<t;i++)
{
res=0;
cin>>n>>k>>s;
for(int j=0;j<n;j++)
cin>>a[j];
dfs(0,0,0);
cout<<res<<endl;
}
return 0;
}
总结
通过剪枝来降低时间复杂度
B 区间选点
题目
数轴上有 n 个闭区间 [a_i, b_i]。取尽量少的点,使得每个区间内都至少有一个点(不同区间内含的点可以是同一个)
Input
数轴上有 n 个闭区间 [a_i, b_i]。取尽量少的点,使得每个区间内都至少有一个点(不同区间内含的点可以是同一个)
Output
第一行1个整数N(N<=100)
第2~N+1行,每行两个整数a,b(a,b<=100)
Examples
Input
2
1 5
4 6
Output
1
Input
3
1 3
2 5
4 6
Output
2
思路
一道贪心的经典题目,我们总是希望使用尽可能少的点覆盖更多的区间,因此,贪心策略为:首先将所有区间按右端点升序排序,然后遍历区间,每次选择未覆盖区间的右端点进行覆盖。
贪心算法的证明
代码实现
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
struct item{
int left;
int right;
bool operator < (const item &a)const
{
if(right!=a.right) return right<a.right;
else return left>a.left;
}
}p[105];
int main()
{
int n;
cin>>n;
for(int i=0;i<n;i++)
{
cin>>p[i].left>>p[i].right;
}
sort(p,p+n);
int now=p[0].right;
int num=1;
for(int i=1;i<n;i++)
{
if(p[i].left>now)
{
now=p[i].right;
num++;
}
}
cout<<num<<endl;
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
Examples
Input
3 10
1 7
3 6
6 10
Output
2
思路
(1)对区间进行预处理,将在[s,t]之外的部分都切掉
(2)按区间的左端点升序排序,若第一个区间的左端点不是s,则无解;若是,继续下一步
(3)依次遍历所有区间,若当前区间的左端点比现有的起始点小,则证明当前的区间已经覆盖了起始点,选择右端点更大的进行更新;若当前区间的左端点比现有的起始点大,则判断现在的终点能否和该区间连接上,若连接不上,则说明有区间未被覆盖,无解;若能连接上,则选择右端点更大的进行更新
(4)当更新后的终点≥t时,输出结果
实现代码
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
struct item{
int left;
int right;
bool operator < (const item &a)const
{
if(left!=a.left) return left<a.left;
}
}p[25003];
int main()
{
int n,t;
scanf("%d%d",&n,&t);
for(int i=0;i<n;i++)
{
scanf("%d%d",&p[i].left,&p[i].right);
if(p[i].left<1) p[i].left=1;
if(p[i].right>t) p[i].right=t;
}
sort(p,p+n);
if(p[0].left!=1)
{
printf("-1\n");
return 0;
}
int count=1;
int start=1,last=1;
int i=0;
bool judge=false;
for(int i=0;i<n;i++)
{
if(p[i].left<=start)
{
last=max(p[i].right,last);
}
else
{
count++;
start=last+1;//记得加一!!!
if(p[i].left<=start)
{
last=max(p[i].right,last);
}
else break;
}
if(last>=t)
{
judge=true;
break;
}
}
if(judge!=true) printf("-1\n");
else printf("%d\n",count);
return 0;
}
总结
(1)看清题目要求,整数覆盖!!!(在更新起始点时+1)(ㄒoㄒ)WA了好多次,不认真看题的后果
(2)读入的数据量较大,建议使用scanf读入
(3)TLE的处理:对数据进行剪枝操作(排序后起点不为s,[s,t]之外的区间),降低时间复杂度