ZJM 有 n 个作业,每个作业都有自己的 DDL,如果 ZJM 没有在 DDL 前做完这个作业,那么老师会扣掉这个作业的全部平时分。
所以 ZJM 想知道如何安排做作业的顺序,才能尽可能少扣一点分。
请你帮帮他吧!
输入
包含T个测试用例。输入的第一行是单个整数T,为测试用例的数量。
每个测试用例以一个正整数N开头(1<=N<=1000),表示作业的数量。
然后两行。第一行包含N个整数,表示DDL,下一行包含N个整数,表示扣的分。
输出
对于每个测试用例,您应该输出最小的总降低分数,每个测试用例一行。
样例
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
12345678910
Sample Output
0
3
5
123
上方有三组样例。
对于第一组样例,有三个作业它们的DDL均为第三天,ZJM每天做一个正好在DDL前全部做完,所以没有扣分,输出0。
对于第二组样例,有三个作业,它们的DDL分别为第一天,第三天、第一天。ZJM在第一天做了第一个作业,第二天做了第二个作业,共扣了3分,
题
• 优先考虑价值高的任务,把n 个 deadline 根据分数从大到小排序,依次遍历
• 对于第i个 deadline,根据 ti 从后往前遍历,一旦找到空闲时段则安排为第 i 个 deadline。从ddl往前遍历可以保证第 i 个 deadline的安排时间对其他 deadline 的影响最小,因此结果更优
• 时间复杂度较高,为n方,但是简单
直接用一个数组标记
代码解释:
定义一个结构体ddl,成员有:ddl的时间,任务的分值,jd表示该任务是否完成,初始值设为0,还有任务的编号。重载<号让ddl在堆中按分值大小排列。
在输入ddl信息时,用maxt记下最晚的ddl的时间,在从后往前遍历日期时,从最后一个ddl时间开始–就行了。
从后往前给每天安排任务,每天遍历一遍ddl,找到ddl在当天的加入堆中。这样堆里都是在ddl前可以完成的任务,当堆不为空时,选择堆顶完成,标记任务的 jd = 1 然后pop。
最后统计所有ddl中有哪些任务未完成,累加扣分最后输出。
完整代码
#include <iostream> #include <algorithm> using namespace std; bool cmp(pair<int,int > x, pair<int,int > y) { //first是ddl second是分数 if(x.second == y.second) return x.first > y.first; return x.second < y.second; } int main() { int T,n,sum; cin>>T; while(T--){ cin>>n; sum=0; pair<int,int > a[n]; int mark[10000]={0}; for(int i=0;i<n;i++) cin>>a[i].first; for(int i=0;i<n;i++) cin>>a[i].second; sort(a, a+n, cmp); for(int i=n-1;i>=0;i--) { int ok=0; for(int k=a[i].first;k>0;k-- ) if(mark[k]==0){ mark[k]=1; ok=1; break; } if(ok==0) sum+=a[i].second; } printf("%d\n", sum); } return 0; }
总结
典型贪心问题,只要找到选大尽量放最后的策略即可
B - 四个数列
题目
ZJM 有四个数列 A,B,C,D,每个数列都有 n 个数字。ZJM 从每个数列中各取出一个数,他想知道有多少种方案使得 4 个数的和为 0。
当一个数列中有多个相同的数字的时候,把它们当做不同的数对待。
请你帮帮他吧!
Input
第一行:n(代表数列中数字的个数) (1≤n≤4000)
接下来的 n 行中,第 i 行有四个数字,分别表示数列 A,B,C,D 中的第 i 个数字(数字不超过 2 的 28 次方)
Output
输出不同组合的个数。
样例
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
1234567
Sample Output
5
1
题解
在没学之前想暴力搜,四层循环遍历四个数列,直接找出现了和为0的情况,,复杂度n的四次方,不可行。
采用二分法可以降低复杂度
• 枚举A和B,然后枚举C和D,两重循换
• 枚举C和D的时候计算它的相反数在A和B中出现多少次
代码:
#include <iostream>
#include <algorithm>
using namespace std;
int sum1[16000000];
int sum2[16000000];
int main()
{
int n,mid,l,r,ans = 0; ;
cin>>n;
int a[n],b[n],c[n],d[n];
for(int i=0;i<n;i++)
cin>>a[i]>>b[i]>>c[i]>>d[i];
int count = 0;
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
sum1[count]=a[i]+b[j];
sum2[count]=c[i]+d[j];
count++;
}
}
sort(sum2, sum2 + count);
for(int i=0;i<count;i++)
{
l=0;
r=count-1;
while(l < r)
{
mid =(l+r)/2;
if(sum1[i] + sum2[mid] >= 0)
r = mid;
else
l = mid + 1;
}
while(sum1[i] + sum2[l] == 0 && l < count) //判断是否为0及为0的个数
{
ans++;
l++;
}
}
cout<<ans<<endl;
return 0;
}
总结
用二分法可以大大降低复杂度
在主函数内如果数组过大,可能会runtime error ,可以拿出去
C - TT 的神秘礼物
题目
TT 是一位重度爱猫人士,每日沉溺于 B 站上的猫咪频道。
有一天,TT 的好友 ZJM 决定交给 TT 一个难题,如果 TT 能够解决这个难题,ZJM 就会买一只可爱猫咪送给 TT。
任务内容是,给定一个 N 个数的数组 cat[i],并用这个数组生成一个新数组 ans[i]。新数组定义为对于任意的 i, j 且 i != j,均有 ans[] = abs(cat[i] - cat[j])。试求出这个新数组的中位数,中位数即为排序之后 (len+1)/2 位置对应的数字,’/’ 为下取整。
TT 非常想得到那只可爱的猫咪,你能帮帮他吗?
Input
多组输入,每次输入一个 N,表示有 N 个数,之后输入一个长度为 N 的序列 cat, cat[i] <= 1e9 , 3 <= n <= 1e5
Output
输出新数组 ans 的中位数
样例
Sample Input
4
1 3 2 4
3
1 10 2
1234
Sample Output
1
8
12
题解
如果暴力枚举,复杂度极高,过不了
用二分的思路,可以解决,我们不能直接算出所有的数然后求中位数,但我们知道总数,,我们可以用二分去找,并且比较他之后有多少数,来确定中位数,为了避免绝对值,用最大的数减去最小的数得到的必然为差的最大值,用两次循环和二分,去逼近中值,在循环里,对每一个j,x[i]-x[j]<p,即x[i]-p<x[j],可以找到i的个数,即小于的个数,
完整代码
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int main()
{
int N;
while(scanf("%d",&N)!=EOF)
{
int cat[N];
for(int i=0;i<N;i++)
{
scanf("%d",&cat[i]);
}
sort(cat,cat+N);
int mid = N*(N-1)/2-(N*(N-1)/2-1)/2; //中位数位置
//第一层二分,二分p值,最后的结果也是二分出来中间 的p值
int l=0;
int r=cat[N-1]-cat[0];
int m,re;
while (l<r)
{
m=(l+r)/2;
int sum=0;
for (int j=0; j<N; j++) //对j进行遍历,找出符合的i
{
int ll=0, rr=N-1, num=0;
while (ll<=rr)
{
int mm = (ll + rr)/2;
if (cat[mm] >= cat[j] + m)
{
rr=mm-1;
num=N-mm;
}
else ll=mm+1;
}
cout<<"!--"<<num<<endl;
sum+= num;
}
if (sum>=mid)
l=m+1;
else r=m;
}
l--;
printf("%d\n", l);
}
}
总结
此题比较复杂,多次二分可以大大减少复杂度,但是一定得注意二分的条件和sum是大于的个数,因此需要相减