【20200304程序设计思维与实践 Week3 作业】

目录

  • A-选数问题
    • 题意:
    • 思路:
    • 总结:
    • 代码:
  • B-区间选点
    • 题意:
    • 思路:
    • 总结:
    • 代码:
  • C - 区间覆盖
    • 题意:
    • 思路:
    • 总结&疑问:
    • 代码:


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!


思路:

使用dfs算法。

设计了递归函数dfs(int idx,int sum,int depth):第一个参数idx记录上一层所选择的数的索引,从大于此索引的位置中取后续选取的数;第二个参数sum传递目前为止的累加和;第三个参数depth记录目前已选择的数的个数(即递归的层数),在每层递归开始时,检查depth是否已达到K,若是则检查和是否为S并相应是否计数。


总结:

每一次调用dfs(),检查完后即遍历剩余的数调用下一层递归。在书写循环的次数时有要注意的地方:每重循环并非都进行到n,而是至少给后续的选数留出足够的位置;如果进行到n再通过判断舍弃,则浪费了大量的无用递归。

AC后又想到了在递归过程中的一个剪枝策略:每次递归开始时做一次判断,在深度没有达到K但累加和已达到S时可以直接终止递归,并回溯,但本题对时间复杂度要求尚未如此严苛,所以没有去实现。


代码:

#include
using namespace std;

int T,n,K,S;
int a[20];
int cot;

void dfs(int idx,int sum,int depth){//深搜递归 
	if(depth==K){//取够数字,检查和 
		if(sum==S){
			cot++;//全局变量计数 
		}
	}
	else{
		for(int p=idx+1;p<=n-K+depth+1;p++){//从余下数字中取下一个数 
		    dfs(p,sum+a[p],depth+1);
		}  
	}
	
}

int main(){
	cin>>T;
	for(int i=1;i<=T;i++){
		cin>>n>>K>>S;
		for(int j=1;j<=n;j++)cin>>a[j];
		cot=0;
		dfs(0,0,0);
		cout<<cot<<endl;
	}
}

B-区间选点

题意:

数轴上有 n 个闭区间 [a_i, b_i]。取尽量少的点,使得每个区间内都至少有一个点(不同区间内含的点可以是同一个)


思路:

贪心策略:所有区间按b从小到大排序(b相同时按a从大到小排序),从最左区间开始,每当遇到一个不含点的区间,选取该区间右端点并更新相关区间。

因为事先已进行排序,所以相关区间必连续且在遇到第一个不相关区间后再也不会有遗漏的相关区间,此时的不相关区间又成为了新的首个不含点区间,从而迭代可行。

实现时设计了区间结构体section,重载比较运算符以实现预处理排序;设计了成员方法jdgHold(int x)用于在循环中检查并更新相关区间,该方法返回布尔值,在主函数中可以与while和迭代游标结合极其简洁地实现循环。


总结:

本题初试贪心算法,对于贪心准则的选取对课堂分析的依赖较大,思考过程以理解积累为主。贪心过程的实现发现了一些书写循环的技巧,例如将循环主要过程封装成函数、返回值直接用做循环条件等。


代码:

#include
#include
using namespace std;

int cot;

struct section{//区间 
	int a,b;
	bool hold;//是否已含有点 
	
	bool operator<(section& s){
		if(b!=s.b)return b<s.b;
		return a>s.a;
	}
	
	bool jdgHold(int x){//每次添加点时 判断与更新 
		if(a>x)return 0;
		else if(a<=x&&x<=b)hold=1;
		return 1;
	}
}; 

section S[105];

int main(){
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)cin>>S[i].a>>S[i].b;
	sort(S+1,S+n+1);//预处理:根据重载的比较运算排序 
	for(int i=1;i<=n;i++){//循环检查+更新 
		if(!S[i].hold){//每当一个区间不包含点,将其右端点加入选点集 
			cot++;//计数 
			int j=i;
			int x=S[i].b;
			while(S[j++].jdgHold(x)){;}//为所有后续区间更新含点状态,直到遇到一个不包含该点的区间 
		}
	}
	cout<<cot<<endl;
}

C - 区间覆盖

题意:

数轴上有 n (1<=n<=25000)个闭区间 [ai, bi],选择尽量少的区间覆盖一条指定线段 [1, t]( 1<=t<=1,000,000)。
覆盖整点,即(1,2)+(3,4)可以覆盖(1,4)。
不可能办到输出-1。


思路:

贪心策略:所有区间按a从小到大排序(a相同时按b从小到大排序),选取第一个区间时,检查所有左端点为1的区间并选择覆盖距离最“远”(右端点最大)的区间;随后,每次选取下一个区间时,检查左端点在上一个区间左右端点之间的全部区间,并选取右端点最大的区间,直到完全覆盖线段。

选择区间时的检查过程是用全局游标实现的。对输入的区间信息进行预处理排序,则使用游标单方向迭代检索必不会遗漏。

当选取第一个区间时不存在左端点为1的区间待选、每次选取下一个区间时不存在左端点在上一个区间左右端点之间的区间,则意味着不存在任何可以使选取继续进行的区间,又由于当前状态是贪心策略的唯一结果,所以此组数据不可能办到覆盖。

记录上一区间的左右端点时要注意细节:对于下一区间的选取,待选区间实际要满足的要求是区间左端点位于( 上一区间左端点上一区间右端点+1]上,同时为判断选取是否能进行下去,检查完的临时记录右端点又要和真实的上一右端点比较是否增大,这就要求在循环中对传递的上一右端点何时+1又何时-1正确分辨。


总结&疑问:

本题的贪心策略在理解课上分析失败的前提下自主思考。继承了上一题预处理排序以单向迭代检查的思路,通过对手工选取区间过程的观察和抽象得到了本题的贪心过程。课件解法中的“切掉”操作描述有些模糊,想知道这和自己的做法有何不同。


代码:

#include
#include
using namespace std;

struct section{//区间 
	int a,b;
	
	bool operator<(section& s){
		if(a!=s.a)return a<s.a;
		return b<s.b;
	}
};

section S[25050]; 

int main(){
	int N,T;
	while(scanf("%d %d",&N,&T)!=EOF){
		for(int i=1;i<=N;i++){
			scanf("%d %d",&S[i].a,&S[i].b);
		}
		sort(S+1,S+N+1);//预处理 
		
		//特判第一种不可能情况 
		if(S[1].a!=1){
			printf("-1\n");
			continue;
		}
		
		int cot=0;
		int pa,pb,idx;
		pa=0;//上一个选取区间的左端点 
		pb=1;//上一个选取区间的右端点+1 
		idx=1;//迭代游标 因为已经预排序 单方向迭代检索必不会遗漏 
		
		bool jdg=0;//成功与否标记 
		while(idx<=N){//每次循环取一个新区间 
			int tempA,tempB;//临时选取区间的左右端点 
	    	tempB=pb-1;//置初值 
    		while(S[idx].a>pa&&S[idx].a<=pb){//是否进入循环以区间左端点为标准 当游标所示区间的左端点位于上一个选取区间的左右端点之间,则检查该区间 
    			if(S[idx].b>tempB){//新的右端点比当前临时右端点更大,则更新临时左右端点为新区间的左右端点 
    				tempB=S[idx].b;
    				tempA=S[idx].a;
    			}
    			idx++;
    		}
    		
    		if(tempB==pb-1)break;//全部检查完毕后右端点没有增大,区间选取没有进行下去的可能,失败 
				
    		cot++; 
    		//更新pa、pb 
    		pb=tempB+1; 
    		pa=tempA;
    		
    		if(pb>T){//进行到此时,线段已全部覆盖,结束循环 
    			jdg=1;
    			break;
			}
		}
		if(jdg)printf("%d\n",cot);
		else printf("-1\n");//失败
	}
}

你可能感兴趣的:(【20200304程序设计思维与实践 Week3 作业】)