Week 4 作业 A-ddl的恐惧 B-四个数列 C-TT的神秘礼物

A-ddl的恐惧

题目描述

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

输入输出格式及样例

Input
输入包含T个测试用例。输入的第一行是单个整数T,为测试用例的数量。
每个测试用例以一个正整数N开头(1<=N<=1000),表示作业的数量。
然后两行。第一行包含N个整数,表示DDL,下一行包含N个整数,表示扣的分。
Output
对于每个测试用例,您应该输出最小的总降低分数,每个测试用例一行。
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
Output
0
3
5

Hint

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

思路

  因为要求最少的扣分,我们采用贪心算法,贪心准则肯定是先处理分扣分最高的,但是仅仅如此因为该任务扣分最多而先处理,它可能影响到更多的任务的安排,所以为了降低每个任务对其他任务的影响,我们让该任务尽可能的踩点做完(就跟踩点上课一样,为了做其他事情而拖延去上课的时间 -_-)。
  所以我们先安排扣分最多的任务,每个任务从他的ddl往前寻找空余时间,如果往前没有空闲时间则放弃该任务,

实验代码

#include <iostream>
#include <string.h>
#include <algorithm>
using namespace std;
struct c {
 int score;//每个任务的扣分
 int ddl;//每个任务的ddl
 bool operator<(const c b) {
  return score > b.score;//优先处理扣分最多的任务
 }
};
bool time[20000];
int main() {
 int T; cin >> T;
 while (T > 0) {
  int n; cin >> n;
  int sums = 0;
  c* test = new c[2000];
  for (int i = 0; i < n; i++)cin >> test[i].ddl;
  for (int i = 0; i < n; i++) {
             cin >> test[i].score;
    sums += test[i].score;//记录总分
  }
  sort(test, test + n);//按扣分降序排序
  int score = 0;//记录得分
  for (int i = 0; i < n+1; i++)time[i] = 0;//初始化每一天的空闲情况
  for (int i = 0; i < n; i++) {
   int j = test[i].ddl;
   for (; j > 0; j--) {
    if (!time[j]) {
     time[j] = 1;//从每个任务当天开始向前安排该任务的处理时间
     score += test[i].score;
     break;
    }
   }
  }
  cout << sums-score << endl;//输出扣分
  T--;
 }
 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
输出不同组合的个数。
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
Output
5

Hint

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

思路

  如果暴力将四个数组的和求出并判断是否等于零,复杂度为n^4,显然超时,所以为了优化,想到二分查找,将四个数组分成两组,将A B两个数组的两两和求出保存在AB数组中,然后找出C D两个数组的两两和的相反数在AB数组中出现的次数,将所有的次数相加就是四个数组和为0的组合数
  关于求一个数在数组中出现的次数,用二分法求出该数第一次出现的位置和最后出现的位置,相减极为数量,大幅优化算法。

实验代码

#include <iostream>
#include <algorithm>
using namespace std;
int findx(int x,int n,int *a) {
 int l1 = 0, r1 = n - 1,ans1 = -1;
 int l2 = 0, r2 = n - 1,ans2 = -1;
 while (l1 <= r1 ) {
  int mid1 = (l1 + r1) >> 1;
  if (a[mid1] <= x) {
   ans1 = mid1 + 1;
   l1 = mid1 + 1;
  }
  else r1 = mid1 - 1;
 }//找出大于x的第一个元素的索引或等于x的最后一个元素的索引
 while (l2 <= r2) {
  int mid2 = (l2 + r2) >> 1;
  if (a[mid2] >= x) {
   ans2 = mid2;
   r2 = mid2 - 1;
  }
  else l2 = mid2 + 1;
 }//找出大于等于x的第一个元素的索引
 if (ans1 == - 1||ans2 == -1)return 0;//不存在该元素 返回0
 else return ans1 - ans2;//返回x的数量
}
int main() {
 int n; cin >> n;
 int* a = new int[n];
 int* b = new int[n];
 int* c = new int[n];
 int* d = new int[n];
 int* ab = new int[n * n];
 for (int i = 0; i < n; i++) 
  cin >> a[i] >> b[i] >> c[i] >> d[i];
 for(int i=0;i<n;i++)
  for (int j = 0; j < n; j++)
   ab[i * n + j] = a[i] + b[j];
 sort(ab, ab + n * n);//升序排序
 int count = 0;
 for (int i = 0; i < n; i++)
  for (int j = 0; j < n; j++) {
   count += findx(-(c[i] + d[j]), n * n, ab);//找出C D两个数组的每个和的相反数在A B两个数组的和数组中出现的次数,极为能使四个数组元素之和等于0的组合次数
  }
 cout << count << 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 的中位数。
Input
4
1 3 2 4
3
1 10 2
Output
1
8

思路

  首先,我们很容易可以想到一种暴力做法,就是建一个数组保存所有的 abs(cat[ i ] - cat[ j ]) ,然后根据中位数的索引得到中位数。
  这种做法很显然算法复杂度太高,数据太大一定会超时,所以为了优化,我们想到了快速查找里面的二分法,通过二分查找可以极大的降低算法复杂度。
  首先,因为我们要求的是新数组里面的中位数,而新数组里面的值是旧数组中任意两个值的差值 的绝对值,所以新数组里的值是介于0旧数组最大值最小值的差这两个值之间,而且有序,所以可以采用二分法查找,对于这之间的任意一个值,求出他在新数组里面的名次,如果大于等于中位数的名次,说明它的值大于等于中位数。
  其次,为了方便求新数组最大值,我们把原数组升序排序,新数组最大值就是cat[ n ]-cat[ 0 ] ,其他则为cat[ j ] - cat[ i ]( 0 <= i < j < n ),为了方便求p(新数组中的元素)的名次,我们只需求出( cat[ j ] - cat[ i ] ) < p 的所有(i , j) 组合即可简化即为 cat[ j ] - < p + cat[ i ] ,由于 j 也是连续的,所以我们可以迭代 i ,二分 j 求出所有的 j 即可。
  总结,本次采用的方法是两次二分法的迭代使用,内部迭代 i 二分 j 求出 p 的名次,外侧二分 p 求出中位数。
  值得注意的是:
    二分 p 的时候,不要当mid的名次等于中位数的名次就停止查找,因为新数组里面的是不一定是连续整数,二分 p 得到的mid在新数组里面不一定存在,所以求出所有名次等于中位数名次的mid里面的最小值即可(即上课是讲的求第一个名次大于等于中位数名次的值的方法)。
    使用scanf替换cin,防止超时

实验代码

#include <iostream>
#include <stdio.h>
#include <algorithm>
using namespace std;
int findx(int x,int n,int* a) {//二分查找找到非递减数组中大于x的第一个元素或等于x的最后一个元素的索引
 int l = 0, r = n - 1, ans = -1;
 while (l <= r) {
  int mid = (l + r) >> 1;
  if (a[mid] <= x) {
   ans = mid;
   l = mid + 1;//去左区间查找
  }
  else r = mid - 1;//去右区间查找
 }
 return ans ;
}
int main() {
 int n;
 while (cin >> n) {
  int* cat = new int[n];
  for (int i = 0; i < n; i++)
   scanf_s("%d", &cat[i]);
  sort(cat, cat + n);//对数组进行升序排序
  int ans_index = (n * (n - 1) / 2 + 1) / 2;//求出新数组中位数的名次
  int l = 0; int r = cat[n - 1] - cat[0];//中位数肯定是位于最大值和最小值之间
  int ans = -1;
  while (l <= r) {//二分求中位数,只需求新数组中名次>=中位数名次的最小值,即为中位数
   //求出名次等于中位数的名次也不一定是中位数,因为通过二分法产生的mid都大于真实的中位数但在新数组中不一定存在,但最小的肯定就是中位数
   int mid = (l + r) >> 1;
   int sum = 0;
   for (int i = 0; i < n; i++) {//对每个mid,遍历i,二分j得到每个i符合条件的j的个数,汇总j的个数就是mid的名次
    int count = findx(cat[i] + mid, n, cat);
    if (count != -1)sum += count - i;
   }
   if (sum >= ans_index) {//左区间
    r = mid - 1;
    ans = mid;
    cout << ans << endl;
   }
   else l = mid + 1;//右区间
  }
  cout << ans << endl;
 }
 return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值