CSP-J 2020 入门级 第一轮 阅读程序(3)

【题目】

CSP-J 2020 入门级 第一轮 阅读程序(3)

01 #include <algorithm>
02 #include <iostream>
03 using namespace std;                     
04                                          
05 int n;                                   
06 int d[50][2];                            
07 int ans;                                 
08                                         
09 void dfs(int n, int sum) {               
10     if (n == 1) {                            
11         ans = max(sum, ans);           
12         return;                                   
13     }                                        
14     for (int i = 1; i < n; ++i) {            
15         int a = d[i - 1][0], b = d[i - 1][1];  
16         int x = d[i][0], y = d[i][1];            
17         d[i - 1][0] = a + x;                     
18         d[i - 1][1] = b + y;                     
19         for (int j = i; j < n - 1; ++j)            
20             d[j][0] = d[j + 1][0], d[j][1] = d[j + 1][1];
21         int s = a + x + abs(b - y);              
22         dfs(n - 1, sum + s);                    
23         for (int j = n - 1; j > i; --j)          
24             d[j][0] = d[j - 1][0], d[j][1] = d[j - 1][1];
25         d[i - 1][0] = a, d[i - 1][1] = b;        
26         d[i][0] = x, d[i][1] = y;                
27     }                                        
28 }                                        
29                                        
30 int main() {                             
31     cin >> n;                                
32     for (int i = 0; i < n; ++i)              
33         cin >> d[i][0];
34     for (int i = 0; i < n;++i)
35         cin >> d[i][1];
36     ans = 0;
37     dfs(n, 0);
38     cout << ans << endl;
39     return 0;
40 }

假设输入的nnn是不超过50的正整数,d[i][0]d[i][1] 都是不超过10000的正整数,完成下面的判断题和单选题:

  • 判断题
  1. 若输入n为0,此程序可能会死循环或发生运行错误。( )
  2. 若输入n为20,接下来的输入全为0,则输出为0。( )
  3. 输出的数一定不小于输入的d[i][0]d[i][1]的任意一个。( )
  • 单选题
  1. 若输入的n为20,接下来的输入是20个9和20个0,则输出为( )。
    A. 1890
    B. 1881
    C. 1908
    D. 1917

  2. 若输入的n为30,接下来的输入是30个0和30个5,则输出为( )。
    A. 2000
    B. 2010
    C. 2030
    D. 2020

  3. (4 分)若输入的n为15,接下来的输入是15到1,以及15到1,则输出为( )。
    A. 2440
    B. 2220
    C. 2240
    D. 2420

【题目考点】

1. 深搜回溯

【解题思路】

05 int n;                                   
06 int d[50][2];                            
07 int ans;
...
30 int main() {                             
31     cin >> n;                                
32     for (int i = 0; i < n; ++i)              
33         cin >> d[i][0];
34     for (int i = 0; i < n;++i)
35         cin >> d[i][1];
36     ans = 0;
37     dfs(n, 0);
38     cout << ans << endl;
39     return 0;
40 }

声明并输入n。d是二维数组,但只有两列,因此实d[i][0]d[i][1]可以认为是一个一维序列第i元素的两个属性。
其中将d[i][0]称为第i元素的第0属性,将d[i][1]成为第i元素的第1属性。

相似例子:保存平面直角坐标系中的很多个点,每个点有x坐标与y坐标,于是设二维数组int p[N][2]p[i][0]表示第i个点的x坐标,p[i][1]表示第i个点的y坐标。p[i]表示一个点,而p[i][0]p[i][1]可以看做是第i元素p[i]的两个属性。

d[i][0]d[i][1]分别输入n个元素,下标从0开始。ans保存结果,初值为0。调用dfs深搜函数后,得到结果ans,并输出。

09 void dfs(int n, int sum) {               
10     if (n == 1) {                            
11         ans = max(sum, ans);           
12         return;                                   
13     }                                        
14     for (int i = 1; i < n; ++i) {            
15         int a = d[i - 1][0], b = d[i - 1][1];  
16         int x = d[i][0], y = d[i][1];            
17         d[i - 1][0] = a + x;                     
18         d[i - 1][1] = b + y;                     
19         for (int j = i; j < n - 1; ++j)            
20             d[j][0] = d[j + 1][0], d[j][1] = d[j + 1][1];
21         int s = a + x + abs(b - y);              
22         dfs(n - 1, sum + s);                    
23         for (int j = n - 1; j > i; --j)          
24             d[j][0] = d[j - 1][0], d[j][1] = d[j - 1][1];
25         d[i - 1][0] = a, d[i - 1][1] = b;        
26         d[i][0] = x, d[i][1] = y;                
27     }                                        
28 }
09 void dfs(int n, int sum) {               

观察深搜函数定义,第一个参数n,传入的是输入的n值。第二个参数sum,看名字大概就是要统计什么的加和。

14     for (int i = 1; i < n; ++i) {            
15         int a = d[i - 1][0], b = d[i - 1][1];  
16         int x = d[i][0], y = d[i][1];            
17         d[i - 1][0] = a + x;                     
18         d[i - 1][1] = b + y;                     

第14行for循环,i从1循环到n-1。
变量a与b保存第i-1元素的第0与第1属性。
变量x与y保存第i元素的第0与第1属性。
而后第i-1元素的第0属性的值变为原来第i-1元素和第i元素的第0属性的加和。
而后第i-1元素的第1属性的值变为原来第i-1元素和第i元素的第1属性的加和。

19         for (int j = i; j < n - 1; ++j)            
20             d[j][0] = d[j + 1][0], d[j][1] = d[j + 1][1];

接下来j从i循环到n-2,不断将第j+1元素的值赋值给第j元素。
如果学过顺序表删除就可以看出,这是在删除第i元素,删除该值后序列的长度减1。

21         int s = a + x + abs(b - y);              
22         dfs(n - 1, sum + s);                    

递归搜索下一层,下一层序列长度为n-1,加和sum增加的值为a+x+∣b−y∣a+x+|b-y|a+x+by
相比于当前这一层递归调用时的序列与下一层递归调用时的序列,二者的区别为:第i-1元素的两属性增加了第i元素两属性的值,而后第i元素被删掉了。
形象地,可以说第i元素与第i-1元素合并成为第i-1元素,合并后的元素的属性值是参与合并的元素对应属性的加和,合并产生的贡献为a+x+∣b−y∣a+x+|b-y|a+x+by,即两元素第0属性的加和,以及两元素第1属性差值的绝对值。

23         for (int j = n - 1; j > i; --j)          
24             d[j][0] = d[j - 1][0], d[j][1] = d[j - 1][1];
25         d[i - 1][0] = a, d[i - 1][1] = b;        
26         d[i][0] = x, d[i][1] = y;                

这一段代码完全还原了第17~20行对d数组的修改,将第i与第i-1元素的值还原到了发生修改之前的状态。符合深搜回溯算法中在递归调用前进行状态更新、在递归调用后进行状态还原的代码结构。

10     if (n == 1) {                            
11         ans = max(sum, ans);           
12         return;                                   
13     }                                        

当n为1时,进入递归出口。sum为已经进行过的合并所产生的贡献加和。使用sum更新合并元素产生贡献加和的最大值ans。最后输出结果ans,为所有合并元素的方案中,贡献加和最大的方案的贡献加和。

最终描述该代码所解决的问题:
长为n的序列中每个元素有两个属性。

  • 合并元素:两元素可以合并为一个元素,设第一个元素的第0属性为a,第1属性为b。第二个元素的第0属性为x,第1属性为y。则合并后元素第0属性为a+xa+xa+x,第1属性为b+yb+yb+y,产生贡献为a+x+∣b−y∣a+x+|b-y|a+x+by
  • 合并方案:每次选择序列中两个相邻的元素进行合并,不断合并元素直到剩下1个元素。
  • 使用深搜回溯算法枚举对序列的所有的合并方案,求出每种合并方案的总贡献,比较得出总贡献最大的方案的总贡献。

明确题目解决的问题后,具体在草纸上做下面的几道问题时,对于这样的递归算法,几乎不可能去模拟程序运行的过程。一般是采用目视法,找规律等方法手算解决问题。

  • 判断题

1. 若输入n为0,此程序可能会死循环或发生运行错误。( )
答:F
若n为0,首先不会输入d数组的初值,进入dfs函数后,递归出口if(n == 1)不满足,跳过。
for(int i = 1; i < n; ++i),该for循环的循环进行的条件i < n也为假,跳过。
没有发生死循环或运行时错误,最后输出0。

2. 若输入n为20,接下来的输入全为0,则输出为0。( )
答:T
由于该序列每个元素的第0与第1属性都为0,每次合并元素的贡献a+x+∣b−y∣a+x+|b-y|a+x+by都为0,任何合并元素的方案得到的贡献加和都为0,因此其最大贡献和也为0,最终输出0。

3. 输出的数一定不小于输入的d[i][0]d[i][1]的任意一个。( )
答:F
反例为:输出的数小于输入的各元素属性值之一。
设两个元素进行合并,一个元素的第0与第1属性的值为a、b,另一个元素第0与第1属性的值为x、y。二者合并的贡献为a+x+∣b−y∣a+x+|b-y|a+x+by。要想使结果很小,那么就要使∣b−y∣|b-y|by最小。当b=yb=yb=y时,∣b−y∣|b-y|by最小,值为0。假设输入的a与x都很小,b与y相等却很大,就可以找到反例。

输入n为2,d[0][0]=1, d[0][1]=10, d[1][0]=1, d[1][1] = 10。即a=1,b=10,x=1,y=10a=1,b=10,x=1,y=10a=1,b=10,x=1,y=10,唯一的合并方案就是将第0与第1元素合并,贡献为a+x+∣b−y∣=1+1+∣10−10∣=2a+x+|b-y|=1+1+|10-10|=2a+x+by=1+1+∣1010∣=2。而222小于d[0][1]d[1][1],是该命题的反例。

找到了反例,因此该命题为假。

  • 单选题

4. 若输入的n为20,接下来的输入是20个9和20个0,则输出为( )。
A. 1890
B. 1881
C. 1908
D. 1917
答:B
有20个元素,第0属性都为9,第1属性都为0。在合并元素时,第1属性的贡献为两元素第1属性b与y数值差值的绝对值∣b−y∣|b-y|by,当b与y都为0时,贡献自然为0。因此本小题只需要考虑第0属性合并时产生的贡献。
两元素合并时,两元素第0属性分别为a与x,合并产生的贡献为a+xa+xa+x
观察规律:
第1次合并一定是两个相邻的9合并,贡献为18。
第2次合并可以是两个相邻的9合并,贡献为18;或18和与其相邻的9合并,贡献为27。选择后者的情况,贡献为27。
第2次合并可以是两个相邻的9合并,贡献为18;或27和与其相邻的9合并,贡献为36。选择后者的情况,贡献为36。
。。。
根据上述规律,每次合并都将序列中的最大值和其相邻的数合并,这样能够得到最大的贡献。

已知序列中每个数的值为a
证明:第i次合并的贡献的最大值为(i+1)a(i+1)a(i+1)a
使用数学归纳法:

  1. 第1次合并的贡献为2a2a2a,命题成立
  2. 假设前k次(k≥1)(k\ge 1)(k1)合并得到的最大贡献为(k+1)a(k+1)a(k+1)a,证明在第k+1次合并能得到的最大贡献为(k+2)a(k+2)a(k+2)a
    如果前k次进行的合并都是最大值与其相邻元素的合并,那么整个序列的最大值为(k+1)a(k+1)a(k+1)a,第k+1k+1k+1次合并时,该值与其相邻元素aaa合并,贡献为(k+2)a(k+2)a(k+2)a
    证明其它情况下合并的贡献必然小于等于(k+2)a(k+2)a(k+2)a
    假设参与合并的两个数分别是经过x次合并与y次合并得到的。那么有x+y≤kx+y\le kx+yk
    第1个数为(x+1)a(x+1)a(x+1)a,第2个数为(y+1)a(y+1)a(y+1)a,二者合并的贡献为(x+y+2)a≤(k+2)a(x+y+2)a\le (k+2)a(x+y+2)a(k+2)a,因此第k+1k+1k+1次合并能获得的最大贡献为(k+2)a(k+2)a(k+2)a,符合命题。
    命题得证。

已知将n个数合并为1个数,无论合并顺序如何,必然都要进行n-1次合并。将序列中的最大值和其相邻的数合并,这样做就能获得本次合并可能得到的最大贡献。
每次合并都采用该方法进行合并,该合并方案为每一次合并得到的贡献都大于等于其它合并方案能得到的贡献。 因此,该合并方案相比于其它合并方案获得的贡献是最大的。

该序列中有20个9,共会进行19次合并,依照上述合并方案:
第1次合并得到贡献2∗92*929
第2次合并得到贡献3∗93*939

第19次合并得到贡献20∗920*9209
总贡献为(2+3+...+20)∗9=(2+20)∗19/2∗9=1881(2+3+...+20)*9=(2+20)*19/2*9=1881(2+3+...+20)9=(2+20)19/29=1881,选B。

5. 若输入的n为30,接下来的输入是30个0和30个5,则输出为( )。
A. 2000
B. 2010
C. 2030
D. 2020
答:C
每个元素第0属性都为0,第1属性不为0,那么在合并元素时,第0属性的贡献a+xa+xa+x都为0。只有第1属性会产生贡献,当一个元素的第1属性为bbb,另一个元素的第1属性为yyy时,合并后元素的第1属性为b+yb+yb+y,两元素合并产生的贡献为∣b−y∣|b-y|by。因此本小题只考虑第1属性合并时产生的贡献。
要想使贡献和最大,需要让每次合并的两数的差值的绝对值尽可能大。两数合并后的值为两数的加和,那么自然想到,可以每次都让数值最大的数与其相邻的数合并。
第1次合并:5与5合并变为10,产生贡献∣5−5∣=0|5-5|=0∣55∣=0
第2次合并:10与5合并变为15,产生贡献∣10−5∣=5|10-5|=5∣105∣=5
第3次合并:15与5合并变为20,产生贡献∣15−5∣=10|15-5|=10∣155∣=10
。。。
根据上述规律,每次合并都将序列中的最大值和其相邻的数合并,这样能够得到最大的贡献。

已知序列中每个数的值为a
证明:第i次合并的贡献的最大值为(i−1)a(i-1)a(i1)a
使用数学归纳法:

  1. 第1次合并的贡献为000,命题成立
  2. 假设前k次(k≥1)(k\ge 1)(k1)合并得到的最大贡献为(k−1)a(k-1)a(k1)a,证明在第k+1次合并能得到的最大贡献为k⋅ak\cdot aka
    如果前k次进行的合并都是最大值与其相邻元素的合并,那么整个序列的最大值为(k+1)a(k+1)a(k+1)a,第k+1次合并时,该值与其相邻元素aaa合并,贡献为∣(k+1)a−a∣=k⋅a|(k+1)a-a|=k\cdot a(k+1)aa=ka
    证明其它情况下合并的贡献必然小于等于k⋅ak\cdot aka
    假设参与合并的两个数分别是经过x次合并与y次合并得到的。那么有x+y≤kx+y\le kx+yk
    第1个数为(x+1)a(x+1)a(x+1)a,第2个数为(y+1)a(y+1)a(y+1)a,二者合并的贡献为∣x+1−y−1∣⋅a=∣x−y∣⋅a|x+1-y-1|\cdot a=|x-y|\cdot ax+1y1∣a=xya
    由于x与y都是非负整数
    所以−y≤y-y\le yyy,因此x−y≤x+yx-y\le x+yxyx+y
    同理−x≤x-x\le xxx,因此y−x≤x+yy-x\le x+yyxx+y
    由于∣x−y∣=x−y|x-y|=x-yxy=xy∣x−y∣=y−x|x-y|=y-xxy=yx,所以∣x−y∣≤x+y|x-y| \le x+yxyx+y
    因此二者合并的贡献∣x−y∣⋅a≤(x+y)a≤k⋅a|x-y|\cdot a \le (x+y)a\le k\cdot axya(x+y)aka
    因此第k+1k+1k+1次合并能获得的最大贡献为k⋅ak\cdot aka,符合命题。
    命题成立。

已知将n个数合并为1个数,要进行n-1次合并。每次将序列中的最大值和其相邻的数合并,这样做就能获得本次合并可能得到的最大贡献。每次都采用该方法完成合并,该合并方案为每一次合并得到的贡献都大于等于其它合并方案能得到的贡献。 因此,该合并方案相比于其它合并方案获得的贡献是最大的。
根据上述合并方案,对长为30每个元素都为5的序列进行合并
第1次合并,得到贡献为(1−1)⋅5=0(1-1)\cdot 5=0(11)5=0
第2次合并,得到贡献为(2−1)⋅5=5(2-1)\cdot 5=5(21)5=5
第3次合并,得到贡献为(3−1)⋅5=10(3-1)\cdot 5=10(31)5=10

第29次合并,得到贡献为(29−1)⋅5=140(29-1)\cdot 5=140(291)5=140
总贡献为(0+1+...+28)⋅5=(1+28)∗28/2∗5=29∗70=2030(0+1+...+28)\cdot 5=(1+28)*28/2*5=29*70=2030(0+1+...+28)5=(1+28)28/25=2970=2030,选C。

6. (4 分)若输入的n为15,接下来的输入是15到1,以及15到1,则输出为( )。
A. 2440
B. 2220
C. 2240
D. 2420
答:C
将每个元素记为(第0属性,第1属性)(第0属性,第1属性)(0属性,第1属性)
根据前两题的经验,无论是第0属性还是第1属性,合并时都要让尽量大的两个数合并,这样产生的贡献是最大的。因为合并较大数能使得数值的绝对值最大,后面无论是做和还是做差,结果都会比较大。
本题不给出具体证明了(考试时肯定也没时间做严密的证明,大概看出一个规律就开始算了)。
第1步,合并(15,15)(15,15)(15,15)(14,14)(14,14)(14,14),得到(15+14,15+14)(15+14,15+14)(15+14,15+14),贡献为15+14+∣15−14∣=15+14+15−14=2∗1515+14+|15-14|=15+14+15-14=2*1515+14+∣1514∣=15+14+1514=215
第2步,合并(15+14,15+14)(15+14,15+14)(15+14,15+14)(13,13)(13,13)(13,13),得到(15+14+13,15+14+13)(15+14+13,15+14+13)(15+14+13,15+14+13),贡献为15+14+13+∣15+14−13∣=15+14+13+15+14−13=2∗(15+14)15+14+13+|15+14-13|=15+14+13+15+14-13=2*(15+14)15+14+13+∣15+1413∣=15+14+13+15+1413=2(15+14)
。。。
第14步,合并(15+...+2,15+...+2)(15+...+2,15+...+2)(15+...+2,15+...+2)与(1,1),得到(15+...+1,15+...+1)(15+...+1,15+...+1)(15+...+1,15+...+1),贡献为15+...+1+∣15+...+2−1∣=15+...+1+15+...+2−1=2∗(15+...+2)15+...+1+|15+...+2-1|=15+...+1+15+...+2-1=2*(15+...+2)15+...+1+∣15+...+21∣=15+...+1+15+...+21=2(15+...+2)

总加和为:
2∗(15+15+14+15+14+13+...15+14+...+2)2*(\\ 15+\\ 15+14+\\ 15+14+13+\\ ...\\ 15+14+...+2)2(15+15+14+15+14+13+...15+14+...+2)
再将结果加上以及减去2∗(15+...+1)2*(15+...+1)2(15+...+1)
结果为:
2∗(2*(2(
15+15+14+15+14+13+...15+14+...+2+15+14+...+2+1)−2∗(15+...+1)=2∗(152+142+...+22+12)−2(1+15)∗15/215+\\ 15+14+\\ 15+14+13+\\ ...\\ 15+14+...+2+\\ 15+14+...+2+1)\\ -2*(15+...+1)\\ =2*(15^2+14^2+...+2^2+1^2)-2(1+15)*15/215+15+14+15+14+13+...15+14+...+2+15+14+...+2+1)2(15+...+1)=2(152+142+...+22+12)2(1+15)15/2
使用平方和公式12+22+...+n2=n(n+1)(2n+1)61^2+2^2+...+n^2=\dfrac{n(n+1)(2n+1)}{6}12+22+...+n2=6n(n+1)(2n+1)
结果为:2∗15(15+1)(2∗15+1)/6−16∗15=30∗16∗31/6−16∗15=2480−240=22402*15(15+1)(2*15+1)/6-16*15\\ =30*16*31/6-16*15=2480-240=2240215(15+1)(215+1)/61615=301631/61615=2480240=2240,选C。

【答案】

  1. F
  2. T
  3. F
  4. B
  5. C
  6. C
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值