PAT乙1005.继续(3n+1)猜想

题目

卡拉兹(Callatz)猜想已经在1001中给出了描述。在这个题目里,情况稍微有些复杂。

当我们验证卡拉兹猜想的时候,为了避免重复计算,可以记录下递推过程中遇到的每一个数。例如对n=3进行验证的时候,我们需要计算3、5、8、4、2、1,则当我们对n=5、8、4、2进行验证的时候,就可以直接判定卡拉兹猜想的真伪,而不需要重复计算,因为这4个数已经在验证3的时候遇到过了,我们称5、8、4、2是被3“覆盖”的数。我们称一个数列中的某个数n为“关键数”,如果n不能被数列中的其他数字所覆盖。

现在给定一系列待验证的数字,我们只需要验证其中的几个关键数,就可以不必再重复验证余下的数字。你的任务就是找出这些关键数字,并按从大到小的顺序输出它们。

输入格式:每个测试输入包含1个测试用例,第1行给出一个正整数K(<100),第2行给出K个互不相同的待验证的正整数n(1<n<=100)的值,数字间用空格隔开。

输出格式:每个测试用例的输出占一行,按从大到小的顺序输出关键数字。数字间用1个空格隔开,但一行中最后一个数字后没有空格。

输入样例:
6
3 5 6 7 8 11
输出样例:
7 6

思考

题意很简单。一开始,我的做法是使用vector容器的迭代器来进行vector内数字的动态删除,但这样做出来的结果是不正确的。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
void check(vector<int> &num, int a){
vector<int>::iterator itr = num.begin();
while(itr != num.end()){
if(*itr == a){
cout << *itr << " ";
itr = num.erase(itr); //删除元素,返回值指向已删除元素的下一个位置
}
else{
itr++;
}
}
}

int main(){
int n;
cin >> n;
vector<int> num;
int k = 0;
while(n--){
int a;
cin >> a;
num.push_back(a);
}
//将读进来的数字存进了vector
vector<int>::iterator iter = num.begin();
while(iter != num.end()){
int ano = *iter;
cout << ano << "ano";

while(ano != 1){
if(ano % 2 == 0){
ano /= 2;
check(num, ano);
}
else{
ano = (3*ano+1) / 2;
check(num, ano);
}
}
iter++;
}
// vector<int>::iterator iter1;
// for(iter1 = num.begin(); iter1!=num.end(); iter1++){
// cout << *iter1 << endl;
// }
}

后来思考,使用iterator错误的原因在于,因为我们在使用erase删除的时候,删除时使用的迭代器会自行往后移动,而我们整体的内容是往前移动的;但在外部的while循环中,iterator依旧会++,会导致num中的某些元素没有被遍历到,因此无法使用iterator。

经过二水马提醒,该用flag来判断该数字是否可被覆盖。但在写的过程中,依旧有些不应该的错误,另外也有些需要注意的地方。先贴出代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <iostream>
#include <map>
#include <cmath>
#include <vector>
#include <algorithm>
using namespace std;

int main(){
int n;
cin >> n;
//int num[100];
//int flag[100];
vector<int> num, flag;
int k = 0;
while(n--){
int a;
cin >> a;
//num[k] = a;
// flag[k] = 1; //不可覆盖
num.push_back(a);
flag.push_back(1);
k++;
}
sort(num.begin(), num.end());
for(int i=0; i<k; i++){
if(flag[i] == 1){
int x = num[i];
while(x != 1){
if(x%2 == 0){
x /= 2;
for(int j=0; j<k; j++){
if(num[j] == x) flag[j] = 0; //可覆盖
}
}
else{
x = (3*x+1)/2;
for(int j=0; j<k; j++){
if(num[j] == x) flag[j] = 0; //可覆盖
}
}
}
}
}
// sort(num, num+k);
int cnt = 0;
for(int i=k-1; i>=0; i--){
if(flag[i] == 1){
cnt ++;
if(cnt == 1){
cout << num[i];
}
else{
cout << " " << num[i];
}
}
}
cout << endl;
}

这次出现错误的有下面几点:
(1)由于普通的数组无法动态的改变数组大小,因此我就先固定num数组大小为100,然后使用k++来往num内填入数字。但是这样会带来一个问题,对num进行sort排序操作时,尽管数组的k以外的部分我没有赋值,但编译时会自动填入数字,这就导致参与排序的数字多了很多我们不知道的数字。因此,解决方案是使用vector来存储数字,这样对其排序时,就不会出现异常数字了。
(2)第二个明显的错误是,我的排序操作是放在修改完flag数组之后进行的,这导致num和flag的下标对不上号。因此num数组的排序应该放在一开始进行。