程序设计思维与实践 Week4作业(DDL的恐惧、四个数列、TT的神秘礼物)

**

A-DDL的恐惧

**
题目描述
ZJM 有 n 个作业,每个作业都有自己的 DDL,如果 ZJM 没有在 DDL 前做完这个作业,那么老师会扣掉这个作业的全部平时分。
所以 ZJM 想知道如何安排做作业的顺序,才能尽可能少扣一点分。
请你帮帮他吧!

Input
输入包含T个测试用例。输入的第一行是单个整数T,为测试用例的数量。
每个测试用例以一个正整数N开头(1<=N<=1000),表示作业的数量。
然后两行。第一行包含N个整数,表示DDL,下一行包含N个整数,表示扣的分。

Output
对于每个测试用例,您应该输出最小的总降低分数,每个测试用例一行。

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

Sample Output

0
3
5

Hint
上方有三组样例。
对于第一组样例,有三个作业它们的DDL均为第三天,ZJM每天做一个正好在DDL前全部做完,所以没有扣分,输出0。
对于第二组样例,有三个作业,它们的DDL分别为第一天,第三天、第一天。ZJM在第一天做了第一个作业,第二天做了第二个作业,共扣了3分,输出3。

解题思路
定义一个结构体,存放每一个作业的ddl和对应的分值。从后向前遍历每一天,将作业中ddl相同的作业压入一个最大堆(根据作业的分值),然后将最大堆的堆首元素删除,相当于将一个作业分配到了这一天。遍历完每一天之后,如果堆中还有元素,则剩下来的作业即为没有成功分配的作业,将其分值相加即为最终结果。
由于是从后向前遍历,所以堆中的每一个作业都可以保证是可以分配到该天的。

C++代码

#include<iostream>
#include<queue>
using namespace std;
struct ddl
{
 int t,s;
 bool operator<(const ddl &p) const
 {
  return s!=p.s ? s<p.s : t<p.t;
 }
};
int main()
{
 int t=0,n=0;
 cin>>t;
 for(int i=0;i<t;i++)
 {
  cin>>n;
  priority_queue<ddl> q;
  ddl *a=new ddl[n];
  for(int j=0;j<n;j++)
  {
   cin>>a[j].t; 
  }
  for(int j=0;j<n;j++)
  {
   cin>>a[j].s; 
  }
  for(int j=n;j>=1;j--)
  {
   for(int k=0;k<n;k++)
   {
    if(a[k].t==j) 
    {
     q.push(a[k]); 
    }
   }
   if(!q.empty()) q.pop();
  }
  int sum=0;
  while(!q.empty()) 
  {
   ddl x=q.top();
   q.pop();
   sum=sum+x.s;
  }
  cout<<sum<<endl;
  delete [] a;
 }
 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

Sample Output

5

Hint
样例解释: (-45, -27, 42, 30), (26, 30, -10, -46), (-32, 22, 56, -46),(-32, 30, -75, 77), (-32, -54, 56, 30).

解题思路
如果采用暴力的做法遍历四个数组,从所有排列组合中找到相应结果,那么复杂度为n^2,会超时。
采用二分的思路。四个数组,两个两个的求和。先遍历数组a和b,将所有的相加结果存放到数组sum中,然后将sum数组从小到大排序。再遍历数组c和d的相加结果x,找到x相反值在sum数组中首次和最后一次出现的位置,将这一段的元素个数加入到ans中,遍历完c和d数组就得到最终结果。

没有AC的情况
原本没有定义两个find函数求位置,而是定义了一个map,遍历a和b的和时,将sum对应的map加一,最后可以得到a和b每一种和的个数。然后再遍历c和d,求c和d求和的相反数对应在map中出现的次数,将其加入到ans中,最后也可以得出最终结果。但是这种方法提交代码超时,这节课主要讲的也是二分的方法,所以改为了上面的做法。

C++代码

#include<iostream>
#include<algorithm>
using namespace std;
int find_first(int x,int n,int *sum)
{
 int l=0,r=n-1,ans=-1;
 while(l<=r)
 {
  int mid=(l+r)/2;
  if(sum[mid]==x)
  {
   ans=mid;
   r=mid-1;
  }
  else if(sum[mid]>=x) r=mid-1;
  else l=mid+1;
 }
 return ans;
}
int find_last(int x,int n,int *sum)
{
 int l=0,r=n-1,ans=-1;
 while(l<=r)
 {
  int mid=(l+r)/2;
  if(sum[mid]==x)
  {
   ans=mid;
   l=mid+1;
  }
  else if(sum[mid]>x) r=mid-1;
  else l=mid+1;
 }
 return ans;
}
int main()
{
 int n=0;
 cin>>n;
 int *a=new int[n];
 int *b=new int[n];
 int *c=new int[n];
 int *d=new int[n];
 int *sum=new int[n*n];
 for(int i=0;i<n;i++)
 {
  cin>>a[i]>>b[i]>>c[i]>>d[i];
 }
 int k=0;
 for(int i=0;i<n;i++)
 {
  for(int j=0;j<n;j++)
  {
   sum[k]=a[i]+b[j];
   k++;
  }
 }
 sort(sum,sum+n*n);
 int ans=0;
 for(int i=0;i<n;i++)
 {
  for(int j=0;j<n;j++)
  {
   int x=c[i]+d[j];
   int l=find_first(-x,n*n,sum);
   int r=find_last(-x,n*n,sum);
   if(l!=-1) ans=ans+(r-l+1);
  }
 }
 cout<<ans<<endl;
 return 0;
}

**

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 的中位数

Sample Input

4
1 3 2 4
3
1 10 2

Sample Output

1
8

解题思路
采用暴力的做法,求出所有的差值排序得出中位数,超时。
所以还是采用二分的方法。在这道题目中,对于中位数p来说,可以通过比较其名次与中位数所在位置的对比来判断p是不是中位数,这个过程可以二分实现;其次p的名次就是xj-xi<=p的二元组的对数,即xj<=xi+p,遍历i得到的所有j 的和,这个过程也可以二分实现。所以共有两个二分过程。
将输入的数组排序,首先二分p,p是差值数组的中位数,所以p的最小值是0,最大值是数组首尾两元素的差值。然后不断地令p等于mid,判断p的名次与中位数位置之间的关系,更改l和r的值,得到最后的结果。其中p的名次通过调用另一个函数计算,函数中遍历数组中的所有元素,对于每一个xi,都对应一个xi+p,然后二分找出数组中第一个大于xi+p的元素位置,该位置到i中间的元素个数加入到sum中,遍历完xi后就可以得出满足不等式xj-xi<=p的二元组个数,即为p的名次。

注意
在二分过程中,l和r的初始化值对应的+1和-1得出的结果是不一样的,注意逻辑关系,否则会WA。并且我在提交过程出现了超时的情况,然后将输入输出流改为scanf和printf就好了。

C++代码

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
int rank_p(int *cat,int p,int n)
{
 int sum=0;
 for(int i=0;i<n-1;i++)
 {
  int l=0,r=n;
  int value=cat[i]+p;
  while(l+1<r)
  {
   int mid=(l+r)/2;
   if(cat[mid]<=value) l=mid;
   else 
   {
    r=mid;
   }
  }
  sum=sum+(l-i);
 }
 return sum;
}
int main()
{
 int n=0;
 while(scanf("%d",&n)!=EOF)
 {
  int *cat=new int[n];
  for(int i=0;i<n;i++)
  {
   scanf("%d",&cat[i]);
  }
  sort(cat,cat+n);
  int len=n*(n-1)/2;
  int median=(len+1)/2;
  int l=0,r=cat[n-1]-cat[0];
  while(l+1<r)
  {
   int p=(l+r)/2;
   int p_rank=rank_p(cat,p,n);
   if(p_rank<median) l=p;
   else r=p;
  }
  printf("%d\n",r);
  delete [] cat;
 }
 return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值