作业
1.选数问题
题目:
https://vjudge.net/contest/360649#problem/A
题意:
给定n个正整数,要求选出k个数,使选出来的k个数和为sum,共有多少个方案?
输入:第一行一个数T(T<=100),表示有T组测试数据。接下来的两行,一行有三个数n,K,S,一行有n个正整数。
输出:每组数据输出一个和为S的方案个数,每组占一行。
思路:
dfs/可行性剪枝
子集枚举问题。枚举所有子集是否合法。
但是枚举全部子集复杂度过高,所以需要优化。
由于合法的方案个数一定不多,而枚举的子集数量较大,所以可行性剪枝可以有效地优化时间。
对于每一个子集,若选的数的个数超过了K,或者选的数的和超过了S,可以直接舍去。
总结:
有多组测试数据时,请一定一定要注意清空的问题!
由于dfs是在所有的可能情况里找可行解,这棵搜索树相当大,所以适当的可行性剪枝/最优性剪枝可以很好地减少搜索,在可能的范围很大但是解相对很少的情况下,要考虑剪枝。
代码:
#include <iostream>
#include <vector>
using namespace std;
int t;
int n,k,s;
int count;
vector<int> a;
//子集排列
void subset(vector<int>& num,int m,int sum) //1-n存子集
{
if(m>n+1) return; //搜索边界
if(num.size()==k&&sum==s)
{
count++;
return;
}
if(num.size()>k||sum>s) return;
num.push_back(a[m-1]); //选
sum=sum+a[m-1];
subset(num,m+1,sum);
num.pop_back(); //不选
sum=sum-a[m-1];
subset(num,m+1,sum);
}
int main()
{
cin>>t;
while(t--)
{
count=0;
cin>>n>>k>>s;
for(int i=0;i<n;i++)
{
int x;
cin>>x;
a.push_back(x);
}
vector<int> num;
subset(num,1,0);
cout<<count<<endl;
a.clear(); //要清空,不然就一直在插入
}
return 0;
}
2.区间选点
题目:
https://vjudge.net/contest/360649#problem/B
题意:
数轴上有 n 个闭区间 [ai, bi]。取尽量少的点,使得每个区间内都至少有一个点(不同区间内含的点可以是同一个)
输入:第一行1个整数N(N<=100);第2~N+1行,每行两个整数a,b(a,b<=100)
输出:一个整数,代表选点的数目
思路:
贪心
希望每次选的数可以包括最多的区间,所以每次取的数都是所有被包括的区间的最大的bi。而为了使所有的区间都被包括,所以第一个取的数一定是bi最小的那个区间的bi。
对于每一个区间[ai, bi],将所有区间按bi升序排列。
用一个变量theEnd记录已经判断过的所有区间的最大的bi,对于剩下的为判断过的区间,将其起点ai与theEnd比较,若ai<=theEnd,这说明该区间可以被目前的bi包括;若ai>theEnd,这说明该区间不能被包括,则需要增加选的数,并将theEnd设为这个区间的bi。
总结:
现在再看自己的代码,貌似while循环和for循环有冗余ヽ(´ー`)┌这个num变量并没什么用,遍历完一遍就行了。
代码:
#include <iostream>
#include <algorithm>
using namespace std;
int n;
int a,b;
struct section
{
int begin,end;
bool flag;
bool operator < (const section& sec) const
{
return end<sec.end;
}
};
section s[100];
int main()
{
cin>>n;
for(int i=0;i<n;i++)
{//input
cin>>a>>b;
s[i].begin=a;
s[i].end=b;
s[i].flag=0; //未被包括
}
//sort
sort(s,s+n);
//贪心
int num=1; //已被包括的区间个数
int count=1;
int theEnd=s[0].end;
s[0].flag=1; //被包括
bool up=0;
while(num<n)
{
up=0;
for(int i=0;i<n&&up!=1;i++)
{
if(s[i].flag==0&&s[i].begin<=theEnd)
{
s[i].flag=1;
num++;
}
if(s[i].flag==0&&s[i].begin>theEnd)
{
theEnd=s[i].end;
s[i].flag=1;
num++;
count++;
up=1; //theEnd被更新
}
}
}
//output
cout<<count<<endl;
return 0;
}
3.区间覆盖
题目:
https://vjudge.net/contest/360649#problem/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。
思路:
贪心
由于题目要求的是覆盖整点,所以先对所有的区间的头尾取整,头向上取整,尾向下取整(然而我怀疑这道题的数据不需要取整,大概全是整数 。
为了覆盖指定线段,应该从左向右考虑,先考虑包括了整点1的区间最长能覆盖到哪个整点(设这个整点为wi),再考虑包括了wi+1的整点的区间最长能覆盖到哪个整点wi+1,一直到覆盖整点t。
所以对所有的区间排序,以起点为第一关键字升序排序,以终点为第二关键字升序排序。
然后从第一个区间开始,若第一个区间的左端点大于1,则不能覆盖指定线段,输出-1。反之,记覆盖的最远的整点为tail,对后面所有未被判断过的区间[ai, bi]进行tail的判断(注意这里的ai, bi指的是取整后的端点)。
我要寻找的是ai<=tail+1且bi>tail的区间里bi最大的那个区间,由于已经对所有区间进行排序,所以只要判断上述条件直到最后一个成立的区间就是我所要找的区间。
最后拿tail与t判断即可。
总结:
贪心准则还是很容易找错的,但仔细想的时候会发现贪心准则和平时我们想的思路很像,所以如果一步一步地细化思路的话,还是可以找到比较正确的贪心准则的。
感觉贪心的证明好难 (〒︿〒) 多多积累经验叭
代码:
#include <stdio.h>
#include <algorithm>
#include <math.h>
using namespace std;
int n;
double t,a,b;
struct section
{
double begin,end;
bool flag;
bool operator < (const section& sec) const
{
if(begin==sec.begin)
return end<sec.end;
return begin<sec.begin;
}
};
section s[25000];
int main()
{
scanf("%d %lf",&n,&t); //n个区间 ,[1,t]
t=floor(t); //下取整,因为只要涵盖整点,且t为正数
for(int i=0;i<n;i++)
{//input
scanf("%lf %lf",&a,&b);
s[i].begin=ceil(a);
s[i].end=floor(b);
s[i].flag=0; //未被访问
}
//sort
sort(s,s+n);
//judge
double head=s[0].begin;
double tail=s[0].end;
//s[0].flag=1;
int count=1; //选中区间的数量
bool ff=0;
if(head<1)
printf("-1"); //不可能
else
{
int i=0;
for(;i<n&&ff!=1;i++)
{
if(s[i].end<=tail&&s[i].flag==0) //被包括的
{
s[i].flag=1;
}
else if(s[i].begin<=tail+1&&s[i].end>tail+1&&s[i].flag==0) //tail需要更新了
{
double tmp=tail;
for(int j=i;j<n&&s[j].begin<=tail+1&&s[j].end>tail+1&&s[j].flag==0;j++)
{
if(s[j].end>tmp)
{
tmp=s[j].end;
s[j].flag=1;
}
}
s[i].flag=1;
tail=tmp;
count++;
}
if(head<=1&&tail>=floor(t))
ff=1; //成功
}
if(i==n&&ff==0)
printf("-1"); //未成功
else
printf("%d",count);
}
return 0;
}