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年保研題:那我呢?)