程序设计Week4-作业 详解
A-DDL的恐惧
Description
ZJM 有 n 个作业,每个作业都有自己的 DDL,如果 ZJM 没有在 DDL 前做完这个作业,那么老师会扣掉这个作业的全部平时分。所以 ZJM 想知道如何安排做作业的顺序,才能尽可能少扣一点分。
输入包含T个测试用例。每个测试用例有N个作业量(1<=N<=1000),分别输入N个作业的DDL和扣分数。
输出每个测试用例最少扣分数。
Sample
input:
3
3
3 3 3
10 5 1
3
1 3 1
6 2 3
7
1 4 6 4 2 4 3
3 2 1 7 6 5 4
output:
0
3
5
Idea
首先针对每个作业自定义结构体包含其ddl和扣分数。对于每个测试实例的N个作业,先按照DDL降序排列,然后从后向前枚举每一天,给每一天安排任务,从数组第一个元素起始,枚举到第x天时,将所有DDL=x的作业加入最大堆(最大堆按照每个作业的扣分数排序),再从最大堆拿出堆顶元素,安排到这一天,否则加入扣分数中,直至枚举完第1天,如果最大堆非空则这些作业都逾期,加入扣分数。最终输出总扣分数
Summary
这道题的基本思想是贪心算法,贪心准则是从最后一天往前推,为每一天安排DDL>=该天的作业中扣分数最大的作业,将扣分数少的留下。
在从后往前枚举第x天时只加入DDL=x的作业可以保证对于第x天时,最大堆中的作业的DDL都大于等于x,不会误将DDL小的作业从堆中拿出导致该作业超时。
Codes
#include <iostream>
#include <algorithm>
#include<cstdio>
#include<cstdlib>
#include <vector>
#include<queue>
using namespace std;
int T;
struct task {
int DDL, s;
//bool operator()(const task &t) {
// return s t.s;
//}
}P[1100];
struct cmp {
bool operator()(task a, task b) {
return a.s < b.s;
}
};
bool compare(const task &a, const task &b) {
return a.DDL > b.DDL;
}
int main()
{
cin.sync_with_stdio(false);
cin >> T;
while (T--) {
int N;
cin >> N;
//memset(P, 0, sizeof(task));
//memset(s, 0, sizeof(int));
int score = 0;
for (int i = 0; i < N; i++)
cin >> P[i].DDL;
for (int i = 0; i < N; i++)
cin >> P[i].s;
sort(P, P + N,compare);
int index = 0;
priority_queue<task,vector<task>,cmp > Q;
for (int t = 1010; t >= 1; t--) {
//if (index >= N)break;
for (; index <N; index++) {
if (P[index].DDL == t) { Q.push(P[index]); }
else if (P[index].DDL < t)break;
}
if (!Q.empty()) {
task temp = Q.top();
Q.pop();
//if (temp.DDL < t) { score += temp.s; }
}
}
while (!Q.empty()) {
task temp = Q.top();
Q.pop();
score += temp.s;
/*cout << temp.s << endl;*/
}
cout << score << endl;
}
}
B-四个数列
Description
ZJM 有四个数列 A,B,C,D,每个数列都有 n 个数字。ZJM 从每个数列中各取出一个数,他想知道有多少种方案使得 4 个数的和为 0。当一个数列中有多个相同的数字的时候,把它们当做不同的数对待。
输入四个数列的n个元素,输出不同组合的个数。
Sample
input:
6
-45 22 42 -16
-41 -27 56 30
-36 53 -37 77
-36 30 -75 -46
26 -38 -10 62
-32 -54 -6 45
output:
5
Idea
首先枚举A和B的每个数,将每个A+B加入一个数列,将该数列升序排序,再枚举C和D的每个数,对于每个C+D,寻找它的相反数在数列中出现的个数,即找到该有序数列中第一个和最后一个 -(C+D),这里需要用到二分查找,分别查找第一个和最后一个,两数相减加入总和,最终的总和就是不同组合的个数。
Summary
一方面,为降低复杂度,应分别枚举AB和CD,如果枚举ABCD的每个数,复杂度会达到四次方,对于四个数总和等于0,相当于A+B=-(C+D)。另一方面,这道题关键在于查找某个数在一个有序数列中第一次出现和最后一次出现的位置,这里使用二分查找,将复杂度降至logn。
Codes
#include <iostream>
#include <vector>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
using namespace std;
int n;
int A[4010];
int B[4010];
int C[4010];
int D[4010];
vector<int> AB;
int sum = 0;
int find1(int x) {
int l = 0, r = AB.size() - 1, ans = -1;
while (l <= r) {
int mid = l + r >> 1;
if (AB[mid] == x) {
ans = mid;
r = mid - 1;
}
else if (AB[mid] > x)r = mid - 1;
else l = mid + 1;
}
return ans;
}
int find2(int x) {
int l = 0, r = AB.size() - 1, ans = -1;
while (l <= r) {
int mid=(l + r) >> 1;
if (AB[mid] == x) {
ans = mid;
l = mid + 1;
}
else if (AB[mid] > x)r = mid - 1;
else l = mid + 1;
}
return ans;
}
int main()
{
cin.sync_with_stdio(false);
cin >> n;
for (int i = 0; i < n; i++)
cin >> A[i] >> B[i] >> C[i] >> D[i];
for(int i=0;i<n;i++)
for (int j = 0;j < n; j++)
{
AB.push_back(A[i] + B[j]);
}
sort(AB.begin(), AB.end());
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++) {
int temp = -(C[i] + D[j]);
int first = find1(temp);
int last = find2(temp);
if (first != -1 && last != -1) { sum += last - first + 1; }
}
cout << sum << endl;
}
C-TT的神秘礼物
Description
给定一个 N 个数的数组 cat[i],并用这个数组生成一个新数组 ans[i]。新数组定义为对于任意的 i, j 且 i != j,均有 ans[] = abs(cat[i] - cat[j]),1 <= i < j <= N。试求出这个新数组的中位数,中位数即为排序之后 (len+1)/2 位置对应的数字,’/’ 为下取整。
多组输入,每次输入一个 N,表示有 N 个数,之后输入一个长度为 N 的序列 cat, cat[i] <= 1e9 , 3 <= n <= 1e5 。输出新数组 ans 的中位数
Sample
input:
4
1 3 2 4
3
1 10 2
output:
1
8
Idea
首先排序原数组算出新数组元素值的范围,在这个范围中二分答案查找一个P,P是中位数,对于每个P计算P在新数列的名次。
因为原数列已排序,可以去绝对值,得Xj<=Xi+P,枚举下标i然后计算满足条件的下标j的个数,这里的计算也使用二分查找找到第一个大于或等于Xi+P的数的位置,而最终所有满足条件的 j 的总和即符合条件的二元组对数,比较该数和中位数的名次,如果小于中位数的名次则中位数在右侧,如果大于中位数的名次则中位数在左侧,相应更新新数组元素值的范围再进行查找,如果两数相等,则该P就是中位数。
假设4个数,分别为1,3,2,4
二分答案操作如下:
Summary
二分答案P,枚举i+二分计算P的名次
由于在代码中从0开始存储元素,中位数即为排序之后 (len+1)/2-1 位置对应的数字。这道题关键在于对于给定的一个数如何判断它是不是中位数,而不是把新数列计算出来取它的中位数,新数列的元素一定属于[0,cat_max-cat_min]。在如何计算二元组对数中思考了比较久。
Codes
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int N;
int cat[100010];
int find(int x) {
int l = 0, r = N - 1, ans = N;
while (l <= r) {
int mid = l + r >> 1;
if (cat[mid] >= x) {
ans = mid;
r = mid - 1;
}
else if (cat[mid] > x)r = mid - 1;
else l = mid + 1;
}
return ans;
}
int main()
{
while (scanf("%d",&N)!=EOF) {
for (int i = 0; i < N; i++)
scanf("%d",&cat[i]);
sort(cat, cat + N);
int min = 0, max = cat[N - 1] - cat[0],P;
int index = (N*(N - 1) / 2 + 1) / 2-1;
//二分答案
while (min < max) {
P = (min + max) >> 1;
int pos = 0;
for (int i = 0; i < N; i++)
pos += N - find(cat[i] + P); //枚举i,计算满足条件的j的个数
if (pos >= N*(N - 1) / 2 - index)min = P + 1;
else max = P;
}
printf("%d\n", min - 1);
}
}