A - DDL 的恐惧
题目:
ZJM 有 n 个作业,每个作业都有自己的 DDL,如果 ZJM 没有在 DDL 前做完这个作业,那么老师会扣掉这个作业的全部平时分。
所以 ZJM 想知道如何安排做作业的顺序,才能尽可能少扣一点分。
请你帮帮他吧!
Input
输入包含T个测试用例。输入的第一行是单个整数T,为测试用例的数量。
每个测试用例以一个正整数N开头(1<=N<=1000),表示作业的数量。
然后两行。第一行包含N个整数,表示DDL,下一行包含N个整数,表示扣的分。
Output
对于每个测试用例,您应该输出最小的总降低分数,每个测试用例一行。
思路:
这题应使用贪心算法。其贪心策略为:从最后一天开始,从所有未到DDL的作业里选择一个分数最高的安排在当天,这样一直到第一天安排完毕。针对该贪心策略有两种复杂度不同的实现方法,这里我采用的是O(nlogn)的方法。方法为:从最后一天开始,逐步向前一天推进。每到新的一天,就将ddl为该天的所有作业放入最大堆中(最大为分数最大),然后取出最大堆的根元素,将其安排在当天。该过程持续到第一天安排完毕后结束。之后堆中所剩作业分数之和即为最小总降低分数。
代码:
#include <iostream>
#include <queue>
using namespace std;
struct ddl
{
int time;
int point;
};
priority_queue<int> pile;
ddl d[1050];
int arrange[2050];
int main()
{
int n,m;
cin>>n;
for(int i=0;i<n;i++)
{
cin>>m;
for(int j=0;j<m;j++)
{
cin>>d[j].time;
}
int maxtime=0;
for(int j=0;j<m;j++)
{
if(d[j].time>maxtime)
maxtime=d[j].time;
cin>>d[j].point;
}
for(int j=maxtime;j>=1;j--)
{
for(int k=0;k<m;k++)
{
if(d[k].time==j)
pile.push(d[k].point);
}
if(pile.empty())
continue;
arrange[j]=pile.top();
pile.pop();
}
int scores=0;
while(!pile.empty())
{
scores=scores+pile.top();
pile.pop();
}
cout<<scores<<"\n";
}
}
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
输出不同组合的个数。
思路:
该题给人的第一印象便是暴力枚举,但是暴力枚举的复杂度高达n的4次方,显然是无法使用的。再考虑到式子 a+b+c+d=0 可变形为 a+b=-(c+d) 的特殊性。若将两个数组分为一组,进行暴力枚举,再将其中一个合并后的数组升序排列,根据上述式子的特殊性,可以利用二分查找的方法来统计数目。这样一来,复杂度为n的平方乘以logn^2。大大降低了复杂度。
代码:
#include <iostream>
#include <queue>
using namespace std;
struct ddl
{
int time;
int point;
};
priority_queue<int> pile;
ddl d[1050];
int arrange[2050];
int main()
{
int n,m;
cin>>n;
for(int i=0;i<n;i++)
{
cin>>m;
for(int j=0;j<m;j++)
{
cin>>d[j].time;
}
int maxtime=0;
for(int j=0;j<m;j++)
{
if(d[j].time>maxtime)
maxtime=d[j].time;
cin>>d[j].point;
}
for(int j=maxtime;j>=1;j--)
{
for(int k=0;k<m;k++)
{
if(d[k].time==j)
pile.push(d[k].point);
}
if(pile.empty())
continue;
arrange[j]=pile.top();
pile.pop();
}
int scores=0;
while(!pile.empty())
{
scores=scores+pile.top();
pile.pop();
}
cout<<scores<<"\n";
}
}
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]),1 <= i < j <= N。试求出这个新数组的中位数,中位数即为排序之后 (len+1)/2 位置对应的数字,’/’ 为下取整。TT 非常想得到那只可爱的猫咪,你能帮帮他吗?
Input
多组输入,每次输入一个 N,表示有 N 个数,之后输入一个长度为 N 的序列 cat, cat[i] <= 1e9 , 3 <= n <= 1e5
Output
输出新数组 ans 的中位数
思路:
该题给人的第一感觉也是枚举,但在这样庞大的数据量下枚举的n的2次方复杂度显然不行。因此, 这里就另辟蹊径 ,用一种验证中位数的方法来求中位数。一般来说 ,中位数不会大于一组数中的(max-min) ,因此,我们就使用二分法对一组0 - (max - min)的数中寻找名次为 (len + 1) / 2 且存在于新数组中的数。于是,新的问题出现了,如何判断一个数p在新数组中的名次?这里我们可以通过统计 ans[ j ]-ans[ i ] <= p 这一式子中满足 条件的i,j个数,即在 ans[i]+p>=ans[j] 这一式子中满足条件的i的个数,但在作此转化前需要将cat数组升序排列以达到去掉绝对值的效果。而对i的个数的统计,同样可以使用二分查找。但是要注意这两个二分查找的一些细节,因为根据你二分查找的写法不一样,结果可能差异很大,需要不断调试。
代码:
#include <stdio.h>
#include <algorithm>
using namespace std;
int flag=0;
int a[100050];
int rank(int p,int size)
{
int rank=0;
for(int i=0;i<size-1;i++)
{
int left=i+1;int right=size-1;int mid;
int ans=-1;
if(p+a[i]<a[left])
continue;
while(left<=right)
{
mid=(left+right)/2;
if(a[mid]<=p+a[i])
{
ans=mid;
left=mid+1;
}
else
{
right=mid-1;
}
}
if(ans==-1)
continue;
rank=rank+ans-i;
}
return rank;
}
int main()
{
int n;
while(scanf("%d",&n)!=EOF)
{
for(int i=0;i<n;i++)
scanf("%d",&a[i]);
sort(a,a+n);
int max=a[n-1]-a[0];
int left=0;int right=max;int mid;
int ans;
while(left<=right)
{
mid=(left+right)/2;
int r=rank(mid,n);
if(r<(n*(n-1)/2+1)/2)
{
left=mid+1;
}
else
{
ans=mid;
right=mid-1;
}
}
printf("%d\n",ans);
}
}