[六省联考 2017] 期末考试
题目描述
有 nnn 位同学,每位同学都参加了全部的 mmm 门课程的期末考试,都在焦急的等待成绩的公布。
第 iii 位同学希望在第 tit_iti 天或之前得知所有课程的成绩。如果在第 tit_iti 天,有至少一门课程的成绩没有公布,他就会等待最后公布成绩的课程公布成绩,每等待一天就会产生 CCC 不愉快度。
对于第 iii 门课程,按照原本的计划,会在第 bib_ibi 天公布成绩。
有如下两种操作可以调整公布成绩的时间:
- 将负责课程 XXX 的部分老师调整到课程 YYY,调整之后公布课程 XXX 成绩的时间推迟一天,公布课程 YYY 成绩的时间提前一天;每次操作产生 AAA 不愉快度。
- 增加一部分老师负责学科 ZZZ,这将导致学科 ZZZ 的出成绩时间提前一天;每次操作产生 BBB 不愉快度。
上面两种操作中的参数 X,Y,ZX, Y, ZX,Y,Z 均可任意指定,每种操作均可以执行多次,每次执行时都可以重新指定参数。
现在希望你通过合理的操作,使得最后总的不愉快度之和最小,输出最小的不愉快度之和即可。
输入格式
第一行三个非负整数 A,B,CA, B, CA,B,C,描述三种不愉快度,详见【题目描述】;
第二行两个正整数 n,mn, mn,m,分别表示学生的数量和课程的数量;
第三行 nnn 个正整数 tit_iti,表示每个学生希望的公布成绩的时间;
第四行 mmm 个正整数 bib_ibi,表示按照原本的计划,每门课程公布成绩的时间。
输出格式
输出一行一个整数,表示最小的不愉快度之和。
样例 #1
样例输入 #1
100 100 2
4 5
5 1 2 3
1 1 2 3 3
样例输出 #1
6
样例 #2
样例输入 #2
3 5 4
5 6
1 1 4 7 8
2 3 3 1 8 2
样例输出 #2
33
提示
样例解释 1
由于调整操作产生的不愉快度太大,所以在本例中最好的方案是不进行调整;全部的 555 门课程中,最慢的在第 333 天出成绩;
同学 111 希望在第 555 天或之前出成绩,所以不会产生不愉快度;
同学 222 希望在第 111 天或之前出成绩,产生的不愉快度为 (3−1)×2=4(3 - 1) \times 2 = 4(3−1)×2=4;
同学 333 希望在第 222 天或之前出成绩,产生的不愉快度为 (3−2)×2=2(3 - 2) \times 2 = 2(3−2)×2=2;
同学 444 希望在第 333 天或之前出成绩,所以不会产生不愉快度;
不愉快度之和为 4+2=64 + 2 = 64+2=6。
数据范围
Case # | n,m,ti,bin, m, t_i, b_in,m,ti,bi | A,B,CA, B, CA,B,C |
---|---|---|
1, 2 | 1≤n,m,ti,bi≤20001 \leq n, m, t_i, b_i \leq 20001≤n,m,ti,bi≤2000 | A=109;B=109;0≤C≤102A = 10^9; B = 10^9; 0 \leq C \leq 10^2A=109;B=109;0≤C≤102 |
3, 4 | 1≤n,m,ti,bi≤20001 \leq n, m, t_i, b_i \leq 20001≤n,m,ti,bi≤2000 | 0≤A;C≤102;B=1090 \leq A; C \leq 10^2; B = 10^90≤A;C≤102;B=109 |
5, 6, 7, 8 | 1≤n,m,ti,bi≤20001 \leq n, m, t_i, b_i \leq 20001≤n,m,ti,bi≤2000 | 0≤B≤A≤102;0≤C≤1020 \leq B \leq A \leq 10^2; 0 \leq C \leq 10^20≤B≤A≤102;0≤C≤102 |
9 - 12 | 1≤n,m,ti,bi≤20001 \leq n, m, t_i, b_i \leq 20001≤n,m,ti,bi≤2000 | 0≤A,B,C≤1020 \leq A, B, C \leq 10^20≤A,B,C≤102 |
13, 14 | 1≤n,m,ti,bi≤1051 \leq n, m, t_i, b_i \leq 10^51≤n,m,ti,bi≤105 | 0≤A,B≤105;C=10160 \leq A, B \leq 10^5; C = 10^{16}0≤A,B≤105;C=1016 |
15 - 20 | 1≤n,m,ti,bi≤1051 \leq n, m, t_i, b_i \leq 10^51≤n,m,ti,bi≤105 | 0≤A,B,C≤1050 \leq A, B, C \leq 10^50≤A,B,C≤105 |
思路
程序所求的不愉快度是一个关于公布天数的函数,该函数是一个下凹函数,即它有最小值,该程序让我们求这个最小值。
由以上分析,我们可以对该题使用三分法求最小值,但这时需要注意区间为整数,故循环的退出条件应该为区间长度小于等于 222。
区间的右端点就是预计公布日期的最大值,可以在每次读取预计公布日期的时候来更新最大值。
计算不愉快度的函数可以分为部分(这是因为这样做方便了一个特例),一部分计算通过实施操作 1 和操作 2 所产生的不愉快度,另一部分计算公布成绩日期太迟导致学生们产生的不愉快度。
特例
C 的值为 1016 使,就算需要将 105 门课程的公布成绩日期调整到第一天最多也才产生 105 * 105 = 1010 的不愉快度,故此时必须在学生中希望日期中的最早日期公布所有成绩,为了寻找这个最早日期,可以在每次输入时都进行更新最早日期。
第一部分
操作 1 会使提前一天公布某科成绩,并且使推迟一天公布某科成绩;操作 2 只会使提前一天公布某科成绩。
如果设定最迟日期 day 公布成绩,则需要根据两种操作产生的不愉快度 A, B 来决定该如何安排这两种操作。
如果 A < B ,则应该尽量多使用操作 1 ,剩下的还需要提前的天数就用操作 2 补齐;否则应该全部使用 2 ,因为如果使用了 x 次操作 1 ,则会多增加 x * (a - b) 的不愉快度。
第二部分
直接用学生希望的日期与日期 day 做比较,如果希望的日期早于 day ,则增加 day 与希望日期的差值 乘 C 的不愉快度。
代码
#include <iostream>
using std::cin, std::cout;
long long A, B, C, ans = 1e16; // 分别为三种不愉快度
int n, m; // n 为学生数量、 m 为课程数量
const int Lim = 100005;
int t[Lim], b[Lim];
int Min(int a, int b) // 用于返回两个 int 类型的值的最小值
{
return (a < b ? a : b);
}
int Max(int a, int b) // 用于返回两个 int 类型的值的最大值
{
return (a > b ? a : b);
}
long long Min(long long a, long long b) // 用于返回两个 long long 类型的值的最大值
{
if (a < b)
return a;
else
return b;
}
long long calculate_AB(int day) // 计算通过操作 A、B 把时间调到 day 的不愉快度
{
// ahead 和 putoff 分别是把原先所有的公布时间 提前 和 推迟 调整到 day 的总天数
long long ahead = 0LL, putoff = 0LL;
for (int i = 0; i < m; i++)
{
if (b[i] > day) ahead += b[i] - day;
else putoff += day - b[i];
}
if (A < B) // 如果操作 1 产生的不愉快度比操作 2 小
return Min(ahead, putoff) * A + (ahead - Min(ahead, putoff)) * B; // 尽量用操作 1
else
return ahead * B; // 否则直接全用操作 2
}
long long calculate_C(int day) // 计算公布日期为 day 导致学生们等待的不愉快度
{
long long sum = 0LL;
for (int i = 0; i < n; i++)
if (t[i] < day)
sum += (day - t[i]) * C;
return sum;
}
int main()
{
cin >> A >> B >> C >> n >> m;
int earliest = 1000000; // 记录学生最早希望公布成绩的日期
int latest = 0; // 记录按计划最晚公布成绩的日期
for (int i = 0; i < n; i++)
{
cin >> t[i];
earliest = Min(earliest, t[i]); // 更新最早日期
}
for (int i = 0; i < m; i++)
{
cin >> b[i];
latest = Max(latest, b[i]); // 更新最晚日期
}
if (C >= 1e16) // 如果 C 太大,就在最早希望日期前公布所有成绩
{
cout << calculate_AB(earliest);
return 0;
}
// 分别为区间左端点、区间右端点、左三等分点、右三等分点
int left = 1, right = latest, midL, midR;
while (right - left > 2)
{
midL = left + (right - left) / 3; midR = right - (right - left) / 3;
// 如果左三等分点所对应的不愉快度 小于 右三等分点的不愉快度
if (calculate_AB(midL) + calculate_C(midL) > calculate_AB(midR) + calculate_C(midR))
left = midL; // 则让子区间的左端点取左三等分点
else
right = midR; // 否则让子区间的右端点取右三等分点
}
long long res; // 用于存储计算出来的不愉快度
for (int i = left; i <= right; i++) // 在最后的小区间中枚举出最小值
{
res = calculate_AB(i) + calculate_C(i);
ans = Min(ans, res); // 更新最小值
}
cout << ans;
return 0;
}