Week3 HomeWork

A-Select Numbers

Given nn positive numbers, ZJM can select exactly KK of them that sums to SS. Now ZJM wonders how many ways to get it!

Input

The first line, an integer T<=100T<=100, indicates the number of test cases. For each case, there are two lines. The first line, three integers indicate nn, KK and SS. The second line, nnintegers indicate the positive numbers.

Output

For each case, an integer indicate the answer in a independent line.

Example

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

Note

Remember that k<=n<=16k<=n<=16 and all numbers can be stored in 32-bit integer

 

思路分析:

  对于这个问题,我们可以使用DFS(深度优先搜索)思想解决,对于每一个number来说,都有两种可能的情况,被选或不被选,每次做出选择之后都要判断它的结束条件——1.已选择的number数目大于等于计划数。2.已选择的number和大于等于计划。3.所有给定的number已经排除完毕。

  根据上述思路就可以写代码了。

 1 #include<iostream>
 2 using namespace std;
 3 
 4 int count=0;
 5 int *p;
 6 int a,b,sum;
 7 void fuc(int i,int size,int add){
 8     if(sum==add&&size==b){
 9         count++;
10         return;
11     }
12     if(add>sum||size>b||i>=a){
13         return;
14     }
15     fuc(i+1,size+1,add+p[i]);
16     fuc(i+1,size,add);
17 }
18 
19 int main(){
20     int c;
21     cin>>c;
22     
23     for(int i=0;i<c;i++){
24         count=0;
25         cin>>a;
26         cin>>b;
27         cin>>sum;
28         if(p!=NULL){
29             delete [] p;
30         }
31         p=new int[a];
32         for(int j=0;j<a;j++){
33             cin>>p[j];
34         }
35         fuc(0,0,0);
36         cout<<count<<endl;
37     }
38 } 

 

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

思路分析及算法设计:

  本题考察贪心算法。贪心算法最重要的是贪心策略的选择。在本题中,我们为了满足题意,可以将所有区间第一顺序位右端点从小到大来排序,第二顺序位左端点从大到小来排序。这样每次选择第一个右端点作为题目所求的点,并且如果下一个区间的左端点小于这个点,就说明这个点在这两个区间的共同区域内,以此类推,即可找出尽可能少的点涵盖所有区间。

以下为源代码:

 1 #include<iostream>
 2 #include<algorithm>
 3 #include<vector>
 4 //发现左端排序有问题
 5 //7 
 6 //1 3
 7 //2 5
 8 //4 6
 9 //6 7
10 //8 10
11 //8 12
12 //11 12 
13  
14 using namespace std;
15 
16 struct node{
17     int a,b;
18     //右端排序 
19     bool operator<(node &n){
20         if(this->b!=n.b){
21             return this->b<n.b; 
22         }else{
23             return this->a<n.a;
24         }
25         
26     }
27 };
28 
29 int main(){
30     int n;
31     cin>>n;
32     if(n<=0){
33         cout<<"error"<<endl;
34     }
35     node *p;
36     p=new node[n]; 
37     int a,b;
38     for(int i=0;i<n;i++){
39         cin>>a;cin>>b;
40         p[i].a=a;p[i].b=b;
41     }
42     sort(p,p+n);
43     //右端升序排序
44     int count=1;
45     int cmp=p[0].b; 
46     for(int i=1;i<n;i++){
47         if(p[i].a<=cmp){
48             
49         }else{
50             count++;
51             cmp=p[i].b;
52         }
53     }
54     cout<<count<<endl;
55 }

 

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



思路分析:

  本题也考察贪心算法。贪心策略是每次选择一个能涵盖最大长度的区间,并且这个区间不会把剩余的待处理的空间分成两块。head和tail分别指剩余空间的起点和终点,输入的数组按照第一顺序位左端点从小到大,右端点从大到小排序。n次循环,设区间【a,b】,若区间左端点a小于等于head,tail=max(tail,b),当tail大于T停止,表示成功覆盖,输出区间数count。循环结束后未停止,表示覆盖失败,输出-1。

 

以下是源代码:

 1 #include<iostream>
 2 #include<algorithm>
 3 
 4 using namespace std;
 5 
 6 struct node{
 7     int a,b;
 8     bool operator<(node & n){
 9         if(this->a!=n.a){
10             return this->a<n.a;
11         }else{
12             return this->b>n.b;
13         }
14     }
15 };
16 
17 int main(){
18     int n,T;
19     cin>>n;cin>>T;
20     node *p;
21     p=new node[n];
22     int count=0;
23     int head=1;
24     int tail=-1;
25     for(int i=0;i<n;i++){
26         scanf("%d %d",&p[i].a,&p[i].b);
27     }
28     sort(p,p+n);
29     for(int i=0;i<n;i++){
30         if(p[i].a<=head){
31             tail=max(tail,p[i].b);
32         }else{
33             count++;
34             head=tail+1;
35             tail=head;
36             if(p[i].a>head){
37                 cout<<-1<<endl;
38                 return 0;
39             }
40             i--;
41         }
42     //    cout<<"head="<<head<<" tail="<<tail<<endl;
43         if(tail>=T){
44             count++;
45             cout<<count<<endl;
46             return 0;
47         }
48     }
49 //    cout<<count<<endl;
50     cout<<"-1"<<endl;
51     return 0;
52 }

 

CSP月模拟测试:

T1

   咕咕东是个贪玩的孩子,有一天,他从上古遗迹中得到了一个神奇的圆环。这个圆环由字母表组成首尾相接的环,环上有一个指针,最初指向字母a。咕咕东每次可以顺时针或者逆时针旋转一格。例如,a顺时针旋转到z,逆时针旋转到b。咕咕东手里有一个字符串,但是他太笨了,所以他来请求你的帮助,问最少需要转多少次。

输入输出

输入只有一行,是一个字符串。
输出最少要转的次数。

 

Week3 HomeWork

 

 

 

思路分析

  这道题比较直白,没有花什么时间最后也直接AC了。看到题目的时候我的第一反应就是这道题要考察取模问题。两点之间的距离,从两个方向出发计算总和为26(圆盘长度),那么按照这个思路这道题就可以解答了。需要注意的是,每次都是从原点转到输入的第一个点,不能在计算的时候忽略了第一步。

  首先我们以字符串格式读入,我们发现这个圆盘是以英文字序排列,我们就可以利用ASCII码的规律将读入字符串转化为整数类型。这样两个点的顺时针距离就可以通过整数的加减直接得到了。但是题意允许按照两个方向走,所以我们还要进一步处理,经过观察,两个点的距离最大不超过13,超过13的部分我们可以通过26-n的公式计算出它对应的最短距离,所以这道题就可以基本完成了。

 

以下是源代码:

 1 #include<iostream>
 2 #include<string>
 3 #include<cstring>
 4 #include<cmath>
 5 using namespace std;
 6 
 7 
 8 
 9 int main(){
10     string s;
11     cin>>s;
12     int *conv;
13     int l=s.length();
14     conv=new int[l+1];
15     for(int i=1;i<l+1;i++){
16         conv[i]=s[i-1]-'a'+1;
17     }
18     conv[0]=1;
19     int sum=0;
20     for(int i=1;i<l+1;i++){
21         int tem=abs(conv[i-1]-conv[i]);
22         if(tem<=13){
23             sum=sum+tem;
24         }else{
25             sum=sum+26-tem;
26         }
27     }
28     cout<<sum<<endl;
29 } 

 

T2

  咕咕东考试周开始了,考试周一共有n天。他不想考试周这么累,于是打算每天都吃顿好的。他决定每天都吃生煎,咕咕东每天需要买ai个生煎。但是生煎店为了刺激消费,只有两种购买方式:①在某一天一次性买两个生煎。②今天买一个生煎,同时为明天买一个生煎,店家会给一个券,第二天用券来拿。没有其余的购买方式,这两种购买方式可以用无数次,但是咕咕东是个节俭的好孩子,他训练结束就走了,不允许训练结束时手里有券。咕咕东非常有钱,你不需要担心咕咕东没钱,但是咕咕东太笨了,他想问你他能否在考试周每天都能恰好买ai​个生煎。

输入输出:

  第一行输入n表示天数,第二行n个数列,表示咕咕东每天要吃的数量。

思路分析:

  一开始做这道题的时候,花了一点时间来理解题意。明白题意之后很快就写出代码了,但是还是花了很多时间在这道题上面。我编造了很多数据来检验我的程序,但是我还是无法证明我的方法一定是正确的。虽然后来収题的时候直接就AC了,但我知道我还是有点运气成份的题眼在于利用奇偶性来做,因为数据能否满足题意完全决定于这串数的奇偶性质。在任意一天,咕咕东要吃的数量为a,减去他手里的券他还要买c个,如果c是奇数,则会产生一个券,若下一天是偶数,还会产生一个券,如果下一天是奇数,就不会产生券了;如果c是偶数,则不会产生券。不满足题意的情况是最后一天了还是产生了券。第一天券的数量设为0。所以我们按照上述思路就可以很快写出代码了。

  如下代码,n、q、c分别表示考试周天数、现有优惠券的数量、咕咕东要买的生煎数量。

 

以下是源代码:

 1 #include<iostream>
 2 using namespace std;
 3 
 4 int main(){
 5     int n;
 6     cin>>n;
 7     int q=0;//quan
 8     for(int i=0;i<n;i++){
 9         int c;
10         cin>>c;
11         c=c-q;
12         q=0;
13         if(c<0){
14             cout<<"NO"<<endl;
15             return 0;
16         }
17         q=c%2;
18     }
19     if(q!=0){
20         cout<<"NO"<<endl;
21         return 0;
22     }
23     cout<<"YES"<<endl;
24     return 0;
25 }

 

T3

  众所周知,瑞神已经达到了CS本科生的天花板,但殊不知天外有天,人外有苟。在浩瀚的宇宙中,存在着一种叫做苟狗的生物,这种生物天生就能达到人类研究生的知识水平,并且天生擅长CSP,甚至有全国第一的水平!但最可怕的是,它可以发出宇宙射线!宇宙射线可以摧毁人的智商,进行降智打击!
宇宙射线会在无限的二维平面上传播(可以看做一个二维网格图),初始方向默认向上。宇宙射线会在发射出一段距离后分裂,向该方向的左右45°方向分裂出两条宇宙射线,同时威力不变!宇宙射线会分裂 次,每次分裂后会在分裂方向前进ai个单位长度。现在瑞神要带着他的小弟们挑战苟狗,但是瑞神不想让自己的智商降到普通本科生zjm那么菜的水平,所以瑞神来请求你帮他计算出共有多少个位置会被"降智打击"。
时间内存限制

  每个测试点 1000ms 262144KB
输入说明

  输入第一行包含一个正整数 n(n<=30),表示宇宙射线会分裂n 次
  第二行包含n个正整数a1,a2···an ,第ai 个数表示第 i次分裂的宇宙射线会在它原方向上继续走多少个单位长度
输出说明

  输出一个数ans ,表示有多少个位置会被降智打击
输入样例

4
4 2 2 3

输出样例

39

思路分析:

  本题没有在CSP模拟赛上没有写出来,直接WA了。后来又遇到了超时的问题,花了一段时间才把这到题彻底AC。通过这道题,我再一次感觉到了自己的不足,要考好CSP,我还有很多要准备。

  还是经验不足,拿到这道题的第一感觉是用数学方法推算一下有没有简便方法。可能看到对称的东西就比较敏感,加之觉得分裂这么多次使用搜索的方法模拟射线太复杂了。事实上经过一顿演算以后,确实发现了简编方法,我们可以很轻易的算出射线经过点的总数(不包括重复的点),然后利用图像的对称性只要算出重复的点的个数即可。但是要想计算重复的还是避免不了重走射线整个的分裂过程,于是这个方法就失败了。

  还是回到搜索的方法,总共射线有八个方向(使用0~7标识),然后分别写出dx【】、dy【】。不同于普通搜索,因为有分裂,我们要把分裂单独写一个函数,然后再这个分裂函数了调用两次类似搜索的函数、同时更改方向速度等参数来模拟这个分裂过程。把这个模型放在一个足够大的坐标中,访问一个点标记一次,随后记录的总数即为答案。

  上述朴素的方法不出意料的超时了。

  接下来就要想着怎么剪枝了,我们在图上模拟以下宇宙射线的轨迹就能发现,当分裂次数比较多的时候,很多情况分裂点会有重叠的情况。虽然这种情况一开始不多,但是要知道,一开始有这种情况,分裂的次数增多的话,后面产生的重复射线就是指数级别的增长,所以我们有必要在这上面进行剪枝的。具体的做法就是再开一个数组,记录每个点的坐标、分裂次数和速度。

  以下代码v【】【】代表坐标轴,visited【】【】【】【】用于上述剪枝,ahead函数用于模拟射线的前进,divide函数表示分裂,传入的参数有坐标(x,y)和分裂速度spd及方向st。

以下是源代码:

 

 1 #include<iostream>
 2 using namespace std;
 3 
 4 
 5 int ans=0;
 6 int n;
 7 int *p;
 8 bool visited[500][500][35][8]={false};
 9 bool v[500][500]={false};
10 int state[]={0,1,2,3,4,5,6,7};//顺时针 
11 int dx[]={0,1,1,1,0,-1,-1,-1};
12 int dy[]={1,1,0,-1,-1,-1,0,1};
13 
14 void ahead(int x,int y,int spd,int st);
15 void divide(int x,int y,int spd,int st);
16 
17 int main(){
18     cin>>n;
19     p=new int[n];
20     for(int i=0;i<n;i++){
21         cin>>p[i];
22     }
23 //    for(int i=0;i<3000;i++){
24 //        for(int j=0;j<3000;j++){
25 //            visited[i][j]=false;
26 //        }
27 //    }
28     
29 //    visited[1000][1000]=true;//原点
30     ahead(250,250,0,0);
31     cout<<ans<<endl;
32     return 0; 
33 } 
34 
35 void divide(int x,int y,int spd,int st){
36     if(spd>=n){
37         return;
38     }
39     if(visited[x][y][spd][st]==true){
40         return;
41     }
42     visited[x][y][spd][st]=true;
43     ahead(x,y,spd,(st+1)%8);
44     ahead(x,y,spd,(st+7)%8);
45 }
46 
47 void ahead(int x,int y,int spd,int st){
48     int xx=x;
49     int yy=y;
50     for(int i=0;i<p[spd];i++){
51         xx=xx+dx[st];
52         yy=yy+dy[st];
53         if(v[xx][yy]==true){
54             continue;
55         }else{
56             v[xx][yy]=true;
57             ans++;
58         }
59     }
60     divide(xx,yy,spd+1,st);
61 }

 

上一篇:机器学习——Week3


下一篇:Java学习心得-week3