浙江大学软件学院2020年保研上机
7-1 Standard Form of Polynomial (20 分)
时间限制:400 ms 内存限制:64 MB
The standard form of a polynomial of degree n is P(x)=anxn+an−1xn−1+⋯+a1x+a0. Given that an=1 and all the n roots { r1, ..., rn } of P(x) are integers. Then P(x) can be written as (x−r1)(x−r2)⋯(x−rn). Your job is to write P(x) in its standard form with all roots given.
Input Specification:
Each input file contains one test case. For each case, the first line gives a positive n (≤10) which is the degree of P(x). Then n integer roots are given in the next line, separated by spaces.
Output Specification:
For each case, output the coefficients ai (i=n-1,⋯,0) in a line. All the numbers must be separated by one space, and there must be no extra space at the beginning or the end of the line. It is guaranteed that all the calculation will be within the range of int.
Sample Input:
3
-1 4 -1
Sample Output:
-2 -7 -4
解析:
求多项式展开,用map存放展开的多项式,其中键存放指数,值存放系数。最后要求按系数从大到小输出,所以用反向迭代器。
#include <vector>
#include <map>
#include <iostream>
using namespace std;
int N;
vector<int> input;
map<int, int> ans;
void test () {
int i, j, t;
scanf("%d", &N);
input.resize(N);
for (i = 0; i < N; i++) scanf("%d", &input[i]);
ans[0] = 1;
for (i = 0; i < N; i++) {
map<int, int> temp, ans1;
temp[1] = 1, temp[0] = -input[i];
for (auto& item : ans) {
for (auto& item1 : temp) {
// 指数相加,系数相乘
ans1[item.first + item1.first] += item.second * item1.second;
}
}
ans = ans1;
}
// 反向迭代
auto it = ans.rbegin();
for (i = 1, it++; it != ans.rend(); it++, i++) {
printf("%d", it->second);
if (i < ans.size() - 1) printf(" ");
}
}
int main () {
test();
return 0;
}
7-2 Distance of Triples (25 分)
时间限制:300 ms 内存限制:64 MB
The distance of triples (三元组) (a,b,c) is defined as D(a,b,c)=∣a−b∣+∣b−c∣+∣c−a∣. Now given three non-empty integer sets S1, S2 and S3, you are supposed to find the minimum distance of all the possible triples (a,b,c) where a∈S1, b∈S2 and c∈S3.
Input Specification:
Each input file contains one test case. For each case, the first line gives three positive integers N1, N2 and N3 (all no more than 104), which are the sizes of S1, S2 and S3, respectively. Then the members of the three sets are given in the following three lines, respectively. All the numbers are integers in the range [−104,104], and they are distinct within each set. The numbers in a line are separated by spaces.
Output Specification:
For each case, print in a line MinD(a, b, c) = d
, where (a, b, c)
are the triples that has the minimum distance, and d
is the corresponding distance. If the solution is not unique, output the largest triples.
Sample Input:
4 4 6
0 9 -1 11
10 -25 11 -10
9 2 41 17 12 30
Sample Output:
MinD(11, 11, 12) = 2
Hint:
Notice that there are two solutions. The other one is MinD(9, 10, 9) = 2
. Since (11, 11, 12) is larger, this one must be printed out.
解析:
方法一:任选一个集合,遍历其中的数字作为a,然后确定其他两个集合中b和c的选择范围。以下按照S1、S2、S3的顺序确定数字。
1、对于任意一个S1中的数字a,为了让距离尽可能小,应选那些尽可能与a接近的数,也就是S2排序后第一个大于等于a的数,以及最后一个小于a的数。当然,如果S2中刚好存在一个等于a的数,那肯定是首选。设从S2中选取的数叫b。
2、现在a和b已经组成了一个区间(或一个点),不妨假设a < b。只要第三个数落在[a, b]区间内,总距离一定最小,而不必考虑第三个数落在区间外的情况。所以第三个数,应该在S3排序后最后一个小于a的数~第一个大于等于b的数中找。最后穷举所有候选序列,计算它们的距离,从中选距离最小且序列最大的。
3、但是这还没完,上面的做法有个问题。例如三个集合:{2} {2, 3, 9} {9},得到(2, 2, 9),实际上(2, 9, 9)才是正确答案。原因在于题目要求给出最大序列,对S2只会从小于等于2的数中选,而S3中可选的数实在是太大了,S2中又有刚好与之“合得来”的数。为此,修改取数顺序为S1→S3→S2,再执行一遍步骤1和步骤2,得到最终结果。
同样也可以先遍历S1作为b,然后去S2中找a、去S3中找c,再去S2中找c、去S3中找a,参见浙江大学软件学院2020年保研上机模拟练习。
#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;
int N1, N2, N3;
vector<int> S1, S2, S3, temp(3);
int min_dist = 0x3fffffff;
vector<vector<int>> ans; // 距离最小的三元组可能有多个,存放所有这些三元组
int dist (int r, int s, int t) {
return abs(r - s) + abs(r - t) + abs(s - t);
}
// 执行思路中的第二步,即根据a与b确定第三个集合的可选范围
void step2 (int a, int b, vector<int> &set3, int index) {
int i;
int left = lower_bound(set3.begin(), set3.end(), min(a, b)) - set3.begin();
left = max(left - 1, 0);
int right = lower_bound(set3.begin(), set3.end(), max(a, b)) - set3.begin();
right = min(right, (int)set3.size() - 1);
for (i = right; i >= left; i--) {
int data3 = set3[i];
int dis = dist(a, b, data3);
if (dis < min_dist) {
min_dist = dis;
temp[index] = data3;
ans.clear();
ans.push_back(temp);
} else if (dis == min_dist) {
temp[index] = data3;
ans.push_back(temp);
}
}
}
void test () {
int i;
scanf("%d %d %d", &N1, &N2, &N3);
// 三个集合放在S1 S2 S3中
S1.resize(N1);
for (i = 0; i < N1; i++) scanf("%d", &S1[i]);
sort(S1.begin(), S1.end());
S2.resize(N2);
for (i = 0; i < N2; i++) scanf("%d", &S2[i]);
sort(S2.begin(), S2.end());
S3.resize(N3);
for (i = 0; i < N3; i++) scanf("%d", &S3[i]);
sort(S3.begin(), S3.end());
for (auto it = S1.rbegin(); it != S1.rend(); it++) {
temp[0] = *it;
// 按S1→S2→S3的顺序,确定S2的范围
int right = lower_bound(S2.begin(), S2.end(), *it) - S2.begin();
// 确定S3的范围
if (right < N2 && S2[right] == *it) {
temp[1] = S2[right];
step2(*it, S2[right], S3, 2);
} else {
right = min(right, N2 - 1);
int left = max(right - 1, 0);
for (i = right; i >= left; i--) {
temp[1] = S2[i];
step2(*it, S2[i], S3, 2);
}
}
// 按S1→S3→S2的顺序,确定S3的范围
right = lower_bound(S3.begin(), S3.end(), *it) - S3.begin();
// 确定S2的范围
if (right < N3 && S3[right] == *it) {
temp[2] = S3[right];
step2(*it, S3[right], S2, 1);
} else {
right = min(right, N3 - 1);
int left = max(right - 1, 0);
for (i = right; i >= left; i--) {
temp[2] = S3[i];
step2(*it, S3[i], S2, 1);
}
}
}
// 对所有距离最小的三元组进行排序
sort(ans.rbegin(), ans.rend());
printf("MinD(%d, %d, %d) = %d", ans[0][0], ans[0][1], ans[0][2], min_dist);
}
int main () {
test();
return 0;
}
方法二:关键思路参见如何求最小三元组距离。这里引用如下:
题目描述:
已知三个升序整数数组a[l], b[m]和c[n]。请在三个数组中各找一个元素,使得组成的三元组距离最小。
三元组的距离定义是:假设a[i]、b[j]和c[k]是一个三元组,那么距离为:Distance = max(|a[i]–b[j]|,|a[i]–c[k]|,|b[j]–c[k]|)请设计一个求最小三元组距离的最优算法,并分析时间复杂度。
关键公式:max(|a[i]–b[j]|,|a[i]–c[k]|,|b[j]–c[k]|) = (abs(a[i]-b[j])+abs(a[i]-c[k])+abs(b[j]-c[k]))/2
假设当前遍历到的这三个数组中的元素分别为a[i],b[j],c[k],并且有a[i]<=b[j]<=c[k],则最小距离肯定是D = c[k]-a[i],那么接下来有三种情况:
- 接下来求a[i],b[j],c[k+1]的最小距离,因为c[k+1]>=c[k],所以,此时的最小距离为c[k+1]-a[i],肯定大于D
- 接下来求a[i],b[j+1],c[k]的最小距离,如果b[j+1]<=c[k],则最小距离不变,如果b[j+1]>c[k],此时的最小距离为b[j+1]-a[i],同样,肯定也是大于D
- 接下来求a[i],b[j+1],c[k]的最小距离,如果a[i+1] < c[k] + (c[k]-a[i]),则此时的最小距离显然会小于D.
所以,我们每次将最小的元素的index加1,才有可能将最小距离更优。所以,整体的思路是开始得出三个数组第一个元素的最小距离,接下来移动最小三个元素中最小元素的下标,与之前得到的最小距离比较,看是否需要更新最小距离,直到遍历完三个数组,时间复杂度为O(l+m+n)
虽然参考里距离的定义与题干不同,但是本质是一样的。不过另一个区别不容忽视:参考里只需要求出来任意一组即可,而题干要求,若有多组解,求出最大的。因此可以稍微变通一下:把数组逆序排序,每次将最大元素的下标加1。这样得到的第一个最小距离的三元组即为所求。逆序排序可以用反向迭代器。
#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;
void test () {
int N1, N2, N3, i, j, k;
int min_dist = 0x3fffffff, dis = 0;
scanf("%d %d %d", &N1, &N2, &N3);
vector<int> a(N1), b(N2), c(N3), ans(3), temp(3);
for (i = 0; i < N1; i++) scanf("%d", &a[i]);
sort(a.rbegin(), a.rend()); // 逆序排序
for (i = 0; i < N2; i++) scanf("%d", &b[i]);
sort(b.rbegin(), b.rend());
for (i = 0; i < N3; i++) scanf("%d", &c[i]);
sort(c.rbegin(), c.rend());
for (i = 0, j = 0, k = 0; i < N1 && j < N2 && k < N3; ) {
temp = {a[i], b[j], c[k]};
dis = abs(a[i] - b[j]) + abs(a[i] - c[k]) + abs(b[j] - c[k]);
if (dis < min_dist){
min_dist = dis;
ans = temp;
}
// 找出当前三元组中最大值
int max1 = *max_element(temp.begin(), temp.end());
if (a[i] == max1) i++;
else if (b[j] == max1) j++;
else k++;
}
printf("MinD(%d, %d, %d) = %d", ans[0], ans[1], ans[2], min_dist);
}
int main () {
test();
return 0;
}
7-3 Partial School Ranking (25 分)
时间限制:400 ms 内存限制:64 MB
In a Group Programming Contest, each university is supposed to send n students who must compete independently. The universities are ranked according to the total score of their students. Your job is to generate the ranklist.
In a Group Programming Contest, each university is supposed to send n students who must compete independently. The universities are ranked according to the total score of their students. Your job is to generate the ranklist.
It sounds like a simple problem, but what makes it complicated is that we have lost all the teams' information! What we have is a list of only some of the students' scores, and some photos taken from the contest sites which shows several students with the same university badge. You just have to recover the ranklist as much as you can.
Input Specification:
Each input file contains one test case. For each case, the first line gives a positive integer N (≤1000). Then N lines follow, each gives the information of a student in the format:
ID
k teammate1⋯teammatek Score
where ID
is a unique 4-digit identification number for a student; k (0≤k≤5) is the number of teammates of this student in a photo; teammatei's are the ID
's of his/her teammate; and Score
is this student's score which is in the range [0, 400].
It is guaranteed that each ID
with a Score
is given only once.
Output Specification:
For each case, first print in a line the number of universities (all the students that are related directly or indirectly as teammates are considered in the same university). Then output the partial school ranking in the format:
ID
S
Scoretotal
where ID
is the smallest student ID in the university; S
is the total number of its students; and Scoretotal is the total score that can be recovered for that university. The universities must be given in descending order of their Scoretotal, or in ascending order of S
if there is a tie, or in ascending order of ID
if there is still a tie.
Sample Input:
11
7456 3 7457 7458 7459 157
6666 3 5551 5552 7777 100
1234 3 5678 9012 0002 80
8888 0 340
2468 3 0001 0004 2222 110
7777 1 6666 57
3721 1 2333 30
9012 3 1236 1235 1234 10
1235 2 5678 9012 50
2222 4 1236 2468 6661 6662 16
2333 4 3721 6661 6662 6663 44
Sample Output:
4
8888 1 340
0001 15 340
5551 4 157
7456 4 157
解析:
有关联的学生都属于同一个学校,要用并查集。用一个字典表示并查集,输入的时候就可以进行合并操作。注意合并的时候,要把大ID合入小ID。然后遍历一遍并查集,把人数、总分等信息都加到父亲(最小ID)的头上。最后再把父亲的这些数据放入vector排序。
#include <vector>
#include <algorithm>
#include <map>
#include <iostream>
using namespace std;
typedef struct Node {
int id, count, grade;
} Node;
int N;
map<int, int> father, grades; // father即并查集,grades存放各学生的分数
map<int, Node> m; // 存放各父亲的数据
vector<Node> ans;
int find_father (int index) {
if (father.count(index) == 0) {
father[index] = index;
return index;
} else {
int x = index;
while (father[x] != x) {
x = father[x];
}
return x;
}
}
void Union (int a, int b) {
int fa = find_father(a);
int fb = find_father(b);
if (fa < fb) {
father[fb] = fa;
}
else father[fa] = fb;
}
bool cmp (Node a, Node b) {
if (a.grade != b.grade) return a.grade > b.grade;
if (a.count != b.count) return a.count < b.count;
return a.id < b.id;
}
void test () {
int i, j, t1, t2, t3, t4;
scanf("%d", &N);
// 边输入边合并
for (i = 0; i < N; i++) {
scanf("%d %d", &t1, &t2);
for (j = 0; j < t2; j++) {
scanf("%d", &t3);
Union(t1, t3);
}
if (t2 == 0) father[t1] = t1;
scanf("%d", &t4);
grades[t1] = t4;
}
// 遍历一遍并查集,把人数、总分等都加到父亲(最小ID)的头上
for (auto &item : father) {
int fa = find_father(item.first);
m[fa].count++;
m[fa].id = fa;
m[fa].grade += grades[item.first];
}
// 把父亲的这些数据放入vector,方便排序
for (auto &item : m) {
ans.push_back(item.second);
}
sort(ans.begin(), ans.end(), cmp);
printf("%d\n", ans.size());
for (auto &item : ans) {
printf("%04d %d %d\n", item.id, item.count, item.grade);
}
}
int main () {
test();
return 0;
}
7-4 Shopping With Coupons (30 分)
时间限制:300 ms 内存限制:64 MB
There are N items on your shopping list, and you have N kinds of coupon that can save you some money on buying any of these items. At the mean time, you have D dollars in your pocket. How can you buy as many items as you can? Here you may use any coupon many times and buy any item many times, but only one coupon may be used to get a reduction of payment for one item once -- that is, for example, you may use coupon A1 to buy item B1, and then use coupon A2 to buy item B1. At the mean time, coupon A1 can be used to buy item B2. But you may not use coupon A1 to buy item B1 AGAIN.
For example, suppose there are 4 items with prices of $10, $12, $15 and $20; and 4 kinds of coupons with values of $6, $7, $8 and $9. If you have $30 in your pocket, the best way to buy is the following:
- buy $10 item for 4 times, with each coupon once, and hence pay $10×4-$6-$7-$8-$9 = $10 in total;
- buy $12 item for 3 times, with each coupons of $7, $8, and $9, and hence pay $12×3-$7-$8-$9 = $12 in total;
- buy $15 item with $9 coupon, and hence pay $6;
Now you have $2 left, not enough to buy any more items. Therefore the maximum number of items you can buy is 8.
Input Specification:
Each input file contains one test case. For each case, the first line gives two positive integers: N (≤105), the number of items (and the coupons), and D (≤106), the amount of money you have.
Then N positive prices are given in the second line, and N positive coupon values are given in the third line. It is guaranteed that the highest value of coupons is less than the lowest price of items, and all the numbers are bounded above by 109. The numbers in a line are separated by spaces.
Output Specification:
Print in a line the maximum number of items you can buy, and the maximum amount of money left, separated by 1 space.
Sample Input:
4 30
12 20 15 10
9 6 8 7
Sample Output:
8 2
解析:
题目大意:有N个商品和N个代金券,每次购买商品只能用一张代金券,并且每张代金券对于同一个商品只能用一次。用给定的钱,问最多能买多少商品,并且最多能剩多少钱。
可以用贪心思想,先买实际花费最少的,再买次小的,以此类推。实际花费即商品价格减去代金券金额,但是如果把所有实际花费都计算出来再排序会超时,所以只能先计算出那些小额的。为此,一种想法是,把商品数组升序排序,代金券数组降序排序,先选择最便宜的商品,每种代金券都用一次,再选次便宜的商品,以此类推。即两层循环,外层遍历商品数组,内层遍历代金券数组。这样做的问题在于便宜商品减去小额代金券,实际花费可能高于贵的商品减去大额代金券,例如题目给的{10,12,15,20}和{9,8,7,6},10-6>12-9。
为了避免这种情况,可以维护一个小数字先出的优先队列,保存实际花费。每次在外层选定一个商品后,通过内层循环把该商品所有实际花费都加入优先队列。接下来把优先队列里那些最优的出队,直到优先队列变空或者钱用完为止。怎样算最优的呢?凡是比下个商品的券后的最便宜价格(即下个商品价格-最大的代金券)还便宜的商品,一定是最优的。然后再在外层选定下一个商品。
以商品{7,8,9,10}和代金券{4,3,2,1}为例。穷举很容易看出,7-4最优。然后,7-3、8-4最优。如果此时钱还足够,7-2、8-3、9-4最优。用优先队列,当第一个商品遍历完券,钱只会用来买7-4和7-3,而7-2、7-1都被放在了优先队列里(因为发现下个商品4块钱就能买,所以7-2和7-1都不是最优的)。当第二个商品遍历完,由于下个商品5块钱就能买,于是7-3、7-2、8-3都被买下,优先队列里有7-1、8-2、8-1。当第三个商品遍历完,由于下个商品6块钱就能买,于是9-4、7-1、8-2、9-3被买下,优先队列里剩8-1、9-2、9-1。到第四个商品,此时已经没有下个商品了,可以全部出队。
因为要用到下个商品的价格,方便起见可以在商品数组末尾加个int的上限值,这样就不用判断是不是最后一个商品了。由于钱最多为106,代金券最多109,不用担心这个假商品真的被买下来。
#include <vector>
#include <algorithm>
#include <queue>
#include <iostream>
using namespace std;
void test () {
int N, D, i, j;
int count1 = 0; // 已经购买的商品数量
priority_queue<int, vector<int>, greater<>> pq;
scanf("%d %d", &N, &D);
vector<int> items(N), coupons(N);
for (i = 0; i < N; i++) scanf("%d", &items[i]);
sort(items.begin(), items.end());
items.push_back((1 << 31) - 1); // 假商品
for (i = 0; i < N; i++) scanf("%d", &coupons[i]);
sort(coupons.rbegin(), coupons.rend()); // 降序排序
for (i = 0; i < N; i++) {
for (j = 0; j < N; j++) {
// 实际花费
pq.push(items[i] - coupons[j]);
}
// 凡是比下个商品的券后的最便宜价格还便宜的商品,一定是最优的
while (!pq.empty() && pq.top() <= items[i + 1] - coupons[0]) {
int data = pq.top();
if (data <= D) {
D -= data;
count1++;
pq.pop();
} else {
// 钱用完了
printf("%d %d", count1, D);
return;
}
}
}
// 所有商品都买了钱还没用完,就会到这里
printf("%d %d", count1, D);
}
int main () {
test();
return 0;
}
总结
编号 | 标题 | 分数 | 类型 |
---|---|---|---|
7-1 | Standard Form of Polynomial | 20 | 5.1 简单数学 |
7-2 | Distance of Triples | 25 | 4.5 二分 |
7-3 | Partial School Ranking | 25 | 9.6 并查集 |
7-4 | Shopping With Coupons | 30 | 4.4 贪心 |
挺难的……至少通过率是这么说的。我个人觉得如果考察的是树、图的那些知识点,一般来说不难,而且往往有模板可以套;而如果考察算法思想,比如贪心、二分等等,就有可能比较难,一方面不一定能想到用什么思想,另一方面这种题可能没有固定的模板可套,这就要足够的敏锐度,以及一定的做题经验。以第二题为例,不能用最暴力的三重遍历,既然要查找,就容易想到二分,那么就要排序。至于方法二,不妨当做套路记下来,以后再遇到三元组问题时就多了种思路。对于第四题,贪心很容易想到,但是如何处理数据结构成为了难点。其他两题比较常规,第一题如果STL足够熟练,应该很快就想到用map,第三题合并校友,就是并查集了。最后总结:大概因为是给保研生做的,再加上20年春秋两次的难度上来了,这套题还是有点难度的。(19年保研题:那我呢?)