WEEK 4 作业题
To sum up
A:DDL的恐惧
n 个作业,每个作业都有自己的 DDL,如在DDL之前完不成作业需要扣除相应的分数。求输入作业安排的最少减分。
1.Sample Input and Outout
Input
输入包含T个测试用例。输入的第一行是单个整数T,为测试用例的数量。
每个测试用例以一个正整数N开头(1<=N<=1000),表示作业的数量。
然后两行。第一行包含N个整数,表示DDL,下一行包含N个整数,表示扣的分。
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
Outout
对于每个测试用例,您应该输出最小的总降低分数,每个测试用例一行。
0
3
5
2.题目思路及代码
一道练习贪心算法的题目。贪心准则是,从最后一个截止日期向前遍历,优先做扣分最多的作业。在完成作业的DDL和扣分值输入后,对作业进行排序,排序第一关键值是DDL日期,第二关键值是扣分值,排序准则均按从小到大。然后从最后一个DDL作为当前date向前遍历,选取目前还未过期的且扣分最多的作业在当前date完成,并在作业数组中删除该项。如果碰到p.back().DDL<date,说明当前日期没有能做的作业,则继续向前遍历,直到遍历到第一天。最后数组中剩下的作业的扣分和即为最优解。
代码如下:
#include<vector>
#include<iostream>
#include<algorithm>
using namespace std;
int n;
int num;
int LostPoint;
class hwk
{
public:
hwk()
{
DDL=0;
de=0;
}
hwk(int a,int b)
{
DDL=a;
de=b;
}
public:
int DDL;
int de;//减分
};
vector<hwk> p;
bool cmp(hwk a,hwk b)
{
if(a.DDL<b.DDL)
return true;
else if(a.DDL==b.DDL&&a.de<b.de)
return true;
else return false;
}
int main()
{
cin>>n;
for(int i=0;i<n;i++)
{
LostPoint=0;
p.clear();
cin>>num;
for(int j=0;j<num;j++)
{
int ddl;
cin>>ddl;
p.push_back({ddl,0});
}//输入作业DDL
for(int j=0;j<num;j++)
{
int De;
cin>>De;
p[j].de=De;
}//输入每个作业相应扣除的分数
sort(p.begin(),p.begin()+num,cmp);
//按照作业优先度排序
for(int date=p.back().DDL;date>=1;date--)
{
//cout<<"date:"<<date<<endl;
if(p.empty())
break;
if(p.back().DDL<date)
continue;
int max=p.size()-1;
int k=p.size()-1;
//挑选所有还未过期且具有最大减分值的作业
while(p[k].DDL>=date&&k>=0) {
//cout<<max<<' ';
if (p[max].de < p[k].de)
max = k;
k--;
}
//将这个作业做掉
p.erase(p.begin()+max,p.begin()+max+1);
}
//计算最优失分值。
while(!p.empty())
{
LostPoint=LostPoint+p.back().de;
p.pop_back();
}
cout<<LostPoint<<endl;
}
return 0;
}
B:四个数列
四个数列 A,B,C,D,每个数列都有 n 个数字。
若 从每个数列中各取出一个数,求多少种方案使得 4 个数的和为 0。
1. Sample Input and Out
Input
第一行:n(代表数列中数字的个数) (1≤n≤4000)
接下来的 n 行中,第 i 行有四个数字,分别表示数列 A,B,C,D 中的第 i 个数字
(数字不超过 2 的 28 次方)
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
Note:
样例解释: (-45, -27, 42, 30), (26, 30, -10, -46), (-32, 22, 56, -46),(-32, 30, -75, 77), (-32, -54, 56, 30).
2.题目思路及代码
是一道练习二分法的题目。由于n<=4000,需要用二分法来降低复杂度。方法是先计算数列A与数列B的和存储起来并排序。后在新数列中二分查找数列C与数列D元素和的相反数。可以将复杂度降到O(n^2+2logn)。在这其中要注意二分查找确定边界时等号的使用。
代码如下:
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int n;
int result=0;
int* A;
int* B;
int* C;
int* D;
vector<int> p1;
void count(int a)
{
int tar1=0;//标记>=a的首个位置
int tar2=0;//标记<=a的最后一个位置
int st=0;//起始指针
int end=p1.size()-1;//末尾指针
int mid=(st+end)/2;//中间指针
//cout<<'!'<<endl;
while(st<=end)
{
//cout<<'!'<<endl;
if(a<=p1[mid]) {
tar1=mid;
end = mid - 1;
mid=(st+end)/2;
}
else {
st = mid + 1;
mid = (st + end) / 2;
}
}
if(p1[tar1]!=a)
return ;
st=0;
end=p1.size()-1;
while(st<=end)
{
if(a>=p1[mid]) {
tar2=mid;
st = mid + 1;
mid=(st+end)/2;
}
else {
end = mid - 1;
mid=(st+end)/2;
}
}
result=result+(tar2-tar1+1);
}
int main()
{
cin>>n;
A=new int[n];
B=new int[n];
C=new int[n];
D=new int[n];
for(int i=0;i<n;i++)
{
cin>>A[i];
cin>>B[i];
cin>>C[i];
cin>>D[i];
}
for(int i=0;i<n;i++) {
for (int j = 0; j < n; j++)
p1.push_back(A[i] + B[j]);
}
sort(p1.begin(),p1.begin()+p1.size());
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
count(-(C[i]+D[j]));
}
cout<<result<<endl;
return 0;
}
C TT的神秘礼物
给定一个 N 个数的数组 cat[i],并用这个数组生成一个新数组 ans[i]。新数组定义为对于任意的 i, j 且 i != j,均有 ans[] = abs(cat[i] - cat[j]),1 <= i < j <= N。试求出这个新数组的中位数,中位数即为排序之后 (len+1)/2 位置对应的数字,’/’ 为下取整。
1. Sample Input and Out
Input
多组输入,每次输入一个 N,表示有 N 个数,之后输入一个长度为 N 的序列 cat, cat[i] <= 1e9 , 3 <= n <= 1e5
4
1 3 2 4
3
1 10 2
Output
输出新数组 ans 的中位数
1
8
2.题目思路及代码
用到了答案二分法求中位数,并且在搜索新数组newList的时候也用到了二分法,一共用到了两次二分。
首先对输入数列进行排序,保证在xj-xi恒为正(j>i)以去掉绝对值。如果输入数列大小为n,则新数列大小N=n*(n-1)/2,如果N为奇数,N++,保证N/2一定为新数列的中位数。
然后进行二分答案,左边界为0,右边界为原数列的最大值减最小值,设中间数P=(l+r)/2。计算满足条件(xj-xi)<=P的二元组个数。如果小于N/2,左边界右移至P+1,反之,有边界左移至P。
现在只需要解决符合条件二元组的计数问题。将条件(xj-xi)<=P改写为xi>=xj+P。在主函数中遍历j,对于每一个j搜索符合条件的i。在find函数中,xj+P为固定值,在原数列中二分查找第一个满足条件>=xj+P的元素下标ans,j-ans 即为数列中对一个确定j符合条件的i的个数。对所有j进行求和,即可得到符合条件二元组个数。
代码如下:
#include<iostream>
#include<algorithm>
#include <cstdio>
using namespace std;
int n,N;
int P,tmp;
int* List;
int l, r, ans,mid;
//返回newList中满足<=P的二元组(i,j)个数
int find(int p,int j)
{
int L = 0, R = j;
ans=j;
while(L<R)
{
mid=(L+R)/2;
if(List[mid]>=List[j]-P)
{
ans=mid;
R=mid;
} else{
L=mid+1;
}
}
return j-ans;
}
int main()
{
while(cin>>n)
{
N=n*(n-1)/2; //新数列的元素个数
if(N%2==1) N++;
List=new int[n];
for(int i=0;i<n;i++)
scanf("%d",&List[i]);
sort(List,List+n);
l = 0;
r = List[n-1]-List[0];
while (l < r)
{
//cout<<l<<' '<<r<<endl;
P = (l+r) /2;
tmp = 0;
for (int j = 1; j < n; j++) //遍历j,寻找xj-xi<=P的个数
{
tmp = tmp + find(P, j);
//cout<<tmp<<endl;
}
if (tmp < N/2) //当计数小于n/2时。
l=P+1;
else //当计数大于等于n/2时。
{
r=P;
}
}
cout<<r<<endl;
}
return 0;
}