PAT第四章专题复习
-
二分法
-
A 1010:给出两个很大数(用字符串来读取)并给出其中一个数的进制,要求另一个数为多少进制时满足两个数相同。如果存在这样的进制,输出进制值;如果不存在,输出“Impossible”.
-
思路:
①首先讲一下这道题当中的坑点,十分的多。第一个,所有的数据应该用long long型来存储,不然肯定溢出; 第二个,即使使用了long long型, 在运算过程中,也有可能溢出,即会发生正数变成负数的情况,这一情况在二分法的while循环if条件判断中需要特别注意。 第三个,最小进制是寻找另一个字符串最大字母;最大进制不一定是给出进制的那个数,也有可能就是寻找的最小进制值,此时对应寻找的进制不存在的情况。
②接着是具体思路。第一步先将给出进制的字符串转为10进制数;第二步寻找没有给出进制的字符串的最小进制和最大进制;第三步进行二分查找。
-
代码实现
//更加精简书写代码,同时注意本题没有明确给出最大进制上限,所以可能会很大。 #include<bits/stdc++.h> using namespace std; //将字符串从radix转为十进制数,注意:当radix很大时,可能会导致计算发生溢出,会从正数变为负数。 long long convert(string n, long long radix){ long long sum = 0; int index = 0, temp = 0; int len = n.length(); for(int i = 0; i < len; i++){ if(n[i] >= '0' && n[i] <= '9'){ sum = sum * radix + (n[i] - '0'); }else sum = sum * radix + (n[i] - 'a') + 10; } return sum; } //寻找一个数的最小进制,也就是找一个字符串中最大的字母。 long long min_radix(string n){ int len = n.length(); int maxn = -1, temp; for(int i = 0; i < len; i++){ if(n[i] >= '0' && n[i] <= '9'){ temp = (n[i] - '0'); }else temp = (n[i] - 'a') + 10; if(temp > maxn){ maxn = temp; } } return maxn + 1; } //二分查找寻找适合的radix long long find_radix(string n, long long num){ //首先寻找字符串n的最小进制,作为二分查找的左边界 long long low = min_radix(n); //注意右边界不一定是num值,也有可能是最小进制数,此时应该对应输出-1 long long high = max(num, low); while(low <= high){ long long mid = (low + high) / 2; long long t = convert(n, mid); if(t == num)return mid; //注意当大数运算时,可能会超过上界,发生正数变成负数。 //大数运算时需要注意这点!!!!! else if(t < 0 || t > num)high = mid - 1; else low = mid + 1; } return -1; } int main(){ string n1, n2; long long tag = 0, radix = 0, result_radix; cin >> n1 >> n2 >> tag >> radix; result_radix = tag == 1? find_radix(n2, convert(n1, radix)) : find_radix(n1, convert(n2, radix)); if(result_radix != -1)printf("%lld", result_radix); else printf("Impossible"); return 0; }
-
-
A 1044:求连续子序列的和并使得这个和等于某个给定的数或者是大于某个给定的数中的最小数(很值得学习)
-
思路:
①sum[i]表示a[1]到a[i]的和。因为a数组的值都是正数,所以sum数组一定是递增的。
②求a[i]-a[j]的和就等于求sum[j] - sum[i-1]的差。
③因为sum数组是单调递增的,就可以考虑用二分法来做此题。首先第一遍寻找值为sum[i-1]+S(S = sum[j] - sum[i-1])的元素是否存在 ;如果存在,则把对应的下标作为右端点j;如果不存在,找到第一个大于S的右端点j。这个需求与函数lower_bound一致。
④通过第一遍,找出最接近答案的nearS,然后第二遍,遍历寻找所有选项使得其序列和等于nearS。注意点:
①lower_bound函数使用,注意最后需要减去数组才可以得到int类型的数组下标值,否则是int*类型的指针值。参数中传入的是数组的起始位置与终点位置,以及衡量标准数x
int a[5] = {1, 2, 3, 4 , 5}; int pos = lower_bound(a, a + 4, x) - x;//寻找第一个大于等于x的数组下标
-
代码实现
#include<bits/stdc++.h> using namespace std; const int N = 100010; int sum[N]; int n, S, nearS = 100000010; int main(){ scanf("%d%d", &n, &S); sum[0] = 0; //初始化sum for(int i = 1;i <= n; i++){ scanf("%d", &sum[i]); sum[i] += sum[i-1]; //求sum数组 } //第一遍遍历寻找nearS for(int i = 1; i <= n; i++){//枚举左端点 //使用lower_bound函数返回数组下标时不要忘记减去数组本身,否则返回的是int*类型而不是int类型 int j = lower_bound(sum+i, sum+n+1, sum[i-1] + S) - sum; if(sum[j] == sum[i-1] + S){ nearS = S; break; }else if(j <= n && sum[j] < sum[i-1] + nearS){ nearS = sum[j] - sum[i-1]; } } //第二遍遍历寻找序列和等于nearS的序列组合 for(int i = 1; i <= n; i++){ int j = lower_bound(sum+i, sum + n + 1, nearS+sum[i-1]) - sum; if(sum[j] == nearS + sum[i-1]){ printf("%d-%d\n", i, j); } } return 0; }
-
-
A 1085:给出一个指定的p,在一个序列中寻找数M <= m * p,使得M与m下标之间的序列个数最多。
-
思路:
①一开始使用的是lower_bound,后来发现使用这个会出现错误,因为lower_bound找到的是第一个大于等于阈值x的下标,如果题目中有多个等于x的数,那就会出错。应该使用upper_bound。
②upper_bound返回的是找到数的下标+1,所以最后减1操作即可。
-
教训:更加熟练使用upper_bound和lower_bound。
-
代码实现
#include<bits/stdc++.h> using namespace std; int main(){ int n, p, a[100010]; cin >> n >> p; for(int i = 0; i < n; i++)cin >> a[i]; sort(a, a + n); int ans = 1; //最大长度,初值为1,至少可以找到一个数,就是它本身) for(int i = 0; i < n; i++){ int j = upper_bound(a+i+1, a+n, (long long)a[i]*p) - a; ans = max(ans, j - i);//upper_bound本身返回的就是值的后面一位,所以要减1.表示最后一个等于或者最后一个小于指定阈值的数 } cout << ans; return 0; }
-
-
-
查找问题:查找数据问题,多试试用二分法来找。可以考虑使用函数lower_bound或者upper_bound或者自己写。
-
排序
-
A 1089:给出原始序列和部分排序后的序列,判断排序是插入排序还是归并排序。
-
思路:
①思路很简单,首先对插排进行查看,是否满足插排。插入排序可以使用for循环搭配sort(a.begin() +i, a.begin() + i+ len);的形式。不满足插排的话那就是归并排序
②然后利用for循环和sort模拟归并排序过程。
注意点:
插入排序起始位置应该从前2个数开始,而不是前一个数。
//易错数据如下: 4 3 4 2 1 3 4 2 1 //输出 2 3 4 1//如果不是从前2个数开始,而是从第一个数开始,这里将会输出3 4 2 1的错误答案
-
代码实现
#include<bits/stdc++.h> using namespace std; vector<int> ori, temp, mid; int n; int main(){ cin >> n; int t, flag = 0, book = 0, flag2 = 0; for(int i = 0; i < n; i++){ cin >> t; ori.push_back(t); } for(int i = 0; i < n; i++){ cin >> t; temp.push_back(t); } //首先进行插入排序 //注意插入排序不对第一个数进行排序,而是从前2个数开始。否则容易产生双解。 /* 数据: 4 3 4 2 1 3 4 2 1 //输出 2 3 4 1 */ for(int i = 2; i < n; i++){ mid = ori; sort(mid.begin(), mid.begin() + i); if(flag){ for(int j = 0; j < n; j++){ printf("%d", mid[j]); if(j != n - 1)printf(" "); } break; } if(mid == temp){ printf("Insertion Sort\n"); flag = 1; } } //然后进行合并排序 if(!flag){//说明是合并排序 printf("Merge Sort\n"); int len = 2; while(len <= n){ mid = ori; for(int i = 0; i < n; i += len){ if(i + len <= n){ sort(mid.begin() + i, mid.begin() + i + len); } else sort(mid.begin() + i, mid.end()); } if(flag2){ for(int i = 0; i < n; i++){ printf("%d", mid[i]); if(i != n-1)printf(" "); } break; } if(mid == temp){ flag2 = 1; } len *= 2; } } return 0; }
-
-
A1012 用数组进行多次排序
-
思路:
①在结构体中,将四个分数定义为数组的形式。然后bool cmp函数可以如下写:
bool cmp(student a, student b){ return a.grade[now] > b.grade[now]; }
②排名rank操作时也利用数组方法。
-
代码实现
#include<bits/stdc++.h> using namespace std; struct student{ int id; int grade[4];//记录4个分数 }stu[2005]; char course[4] = {'A','C','M','E'}; int ranks[1000000][4]; int now;//表示当前按now号分数排序stu数组 bool cmp(student a, student b){ return a.grade[now] > b.grade[now];//此处很精简,值得学习 } int main(){ memset(ranks, 0, sizeof(ranks)); int n, m; scanf("%d%d", &n, &m); for(int i = 0; i < n; i++){ scanf("%d%d%d%d", &stu[i].id, &stu[i].grade[1],&stu[i].grade[2],&stu[i].grade[3]); stu[i].grade[0] = round((stu[i].grade[1]+stu[i].grade[2]+stu[i].grade[3])*1.0/3)+0.5; } for(now = 0; now < 4; now++){ sort(stu, stu + n, cmp); ranks[stu[0].id][now] = 1;//将分数最高的考生设置为1 for(int i = 1; i < n; i++){ //如果与前一位考生分数相同 if(stu[i].grade[now] == stu[i-1].grade[now]){ ranks[stu[i].id][now] = ranks[stu[i-1].id][now];//排名相同 } else{ ranks[stu[i].id][now] = i + 1;//注意,此处不是前一个排名加1,而就是i的大小 } } } int quiry; for(int i = 0; i < m; i++){ scanf("%d", &quiry); if(ranks[quiry][0] == 0){ printf("N/A\n"); } else{ int k = 0; for(int j= 0; j < 4; j++){ if(ranks[quiry][j] < ranks[quiry][k]){ k = j;//选出最小的rank值,同时k值又是按课程的优先级顺序来变化的。此处很巧妙 } } printf("%d %c\n", ranks[quiry][k], course[k]); } } return 0; }
-
-
-
其他有用技巧
-
A 1093 :统计由P, A,T三个字母组成的字符串中PAT的不连续序列个数有多少个。
-
思路:
①可以明确的是,直接暴力会超时。可以换个角度思考。一个指定A字符的PAT个数 = 左边P的个数 x 右边T的个数。那么最终答案就是左右A字符的PAT个数相加。
-
代码实现
#include<bits/stdc++.h> using namespace std; const int maxn = 100010; const int mod = 1000000007; char str[maxn]; int leftNumP[maxn] = {0};//统计每一位左边P的个数 int main(){ cin >> str; int len = strlen(str); for(int i = 0; i < len; i++){ if(i > 0){ leftNumP[i] = leftNumP[i-1];//先继承上一位的结果 } if(str[i] == 'P')leftNumP[i]++; } int ans = 0, rightNumT = 0; for(int i = len - 1; i >= 0; i--){ if(str[i] == 'T'){ rightNumT++; //右边T的个数增加 } else if(str[i] == 'A'){ ans = (ans + leftNumP[i] * rightNumT) % mod; } } cout << ans; return 0; }
-
-
A 1101 :在一个序列中,寻找这样的数——这个数左边所有数小于这个数并且右边所有数大于这个数。寻找这样的数的个数。
-
思路:
①直接暴力肯定超时。可以从做往右遍历寻找每一位数前面最大的数以及从右往左遍历寻找每一位数右边最小的数。
注意:
①本题一开始提交有一个格式错误。一般考虑是不是多输出了空格或者是不是漏输出空行。比如答案个数为0时,下一行是否需要输出空行。
-
代码实现:
#include<bits/stdc++.h> using namespace std; int a[100005]; int main(){ set<int> ans; int n, maxnum[100005] = {0}, minnum[100005]; cin >> n; fill(minnum, minnum + n, 1000000005); for(int i = 0; i < n; i++){ cin >> a[i]; } //寻找每位前面的最大值 if(a[0] > maxnum[0])maxnum[0] = a[0]; for(int i = 1; i < n; i++){ maxnum[i] = max(maxnum[i-1], a[i]); } //寻找每位后面的最小值 if(a[n-1] < minnum[n-1])minnum[n-1] = a[n-1]; for(int i = n - 2; i >= 0; i--){ minnum[i] = min(minnum[i+1], a[i]); } //遍历,寻找符合条件的数字 if(a[0] < minnum[1]){ ans.insert(a[0]); } if(a[n-1] > maxnum[n-2])ans.insert(a[n-1]); for(int i = 1; i < n-1; i++){ if(a[i] > maxnum[i-1] && a[i] < minnum[i+1]){ ans.insert(a[i]); } } printf("%d\n", ans.size()); if(ans.size() != 0){ auto it = ans.begin(); printf("%d", *it); it++; for(; it != ans.end(); it++){ printf(" %d", *it); } }else{ printf("\n"); } return 0; }
-
-