A - DDL 的恐惧
题意:
ZJM 有 n 个作业,每个作业都有自己的 DDL,如果 ZJM 没有在 DDL 前做完这个作业,那么老师会扣掉这个作业的全部平时分。
所以 ZJM 想知道如何安排做作业的顺序,才能尽可能少扣一点分。
请你帮帮他吧!
Input:
输入包含T个测试用例。输入的第一行是单个整数T,为测试用例的数量。
每个测试用例以一个正整数N开头(1<=N<=1000),表示作业的数量。
然后两行。第一行包含N个整数,表示DDL,下一行包含N个整数,表示扣的分。
output:
对于每个测试用例,您应该输出最小的总降低分数,每个测试用例一行。
思路:
已知这是一道贪心算法的习题,需要找出该题的贪心指标。每个ddl的完成时间都是1,优先考虑得分高的作业。这时候需要将分数从高到低排序,如果分数相同,那么就考虑ddl,将相同得分的ddl从低到高排序。ddl和得分score可以构成一个结构体。找到了贪心指标,那就针对第i个ddl,利用bool数组该时间是否已经利用,根据t从前往后遍历,一旦找到空闲时间则将该时段安排。
总结:
这道题一开始只考虑了score排序,没有考虑相同score后需要进一步排序,样例虽然通过了,但还是wa了。对贪心指标一定要考虑严谨,对于一定的情况需要去证明其的合理性。
代码:
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
#define _for(i,a,b) for(int i=a;i<b;i++)
struct ZJM
{
int ddl;
int score;
}z[1010];
bool vis[1010] = { false };
int cmp(const ZJM z1, const ZJM z2)
{
if (z1.score == z2.score)
return z1.ddl < z2.ddl;
return z1.score > z2.score;
}
int main()
{
int T,n;
int a;
cin >> T;
_for(i, 0, T)
{
int count = 0;
int a;
cin >> n;
_for(i, 0, n)
{
cin >> z[i].ddl;
}
_for(i, 0, n)
{
cin >> z[i].score;
}
sort(z, z + n,cmp);
memset(vis, 0, sizeof(vis));
_for(i, 0, n)
{
for (a = z[i].ddl; a >= 1; a--)
{
if (vis[a] == false)
{
vis[a] = true;
break;
}
}
if (a == 0) count += z[i].score;
}
cout << count << endl;
}
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:
输出不同组合的个数。
思路:
一开始按学长所讲,枚举数列A+数列B,再枚举数列C+数列D。然后计算C+D的相反数在A和B中出现了多少次。不过在写的代码还是不小心将复杂度为O(N^4)
_for(i, 0, n*n)
{
_for(j, 0, n * n)
{
if (sumab[i] == -sumcd[j])
count++;
}
}
很显然,肯定是TE的,这时候考虑了降低复杂度的二分方法,首先是将枚举数列C+D的所有数据保存的数组sumcd从小到大排序,然后从sumab依次进行枚举,初始化start和end,将start为0,end为(n-1)*(n-1),然后利用二分将sumab[i]与sumcd[mid]进行比较,找到相加等于0的最小的位置,然后再次进行循环,因为sumcd是有序的,若相同的数字出现多次,则一直向右加起来,这样遍历完sumab即可得到符合的个数。
代码:
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
#define _for(i,a,b) for(int i=a;i<b;i++)
#define MAXN 4000
int a[MAXN], b[MAXN], c[MAXN], d[MAXN],sumab[MAXN*MAXN],sumcd[MAXN*MAXN];
int main()
{
int count = 0;
int n;
cin >> n;
_for(i, 0, n)
{
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++)
{
sumab[k] = a[i] + b[j];
sumcd[k] = c[i] + d[j];
k++;
}
}
sort(sumcd, sumcd + k);
for (int i = 0; i <k; i++)
{
int start =0, end =k-1;
while (start < end)
{
int mid = (start + end) >> 1;
if (sumab[i] + sumcd[mid] >= 0)
{
end = mid;
}
else start = mid + 1;
}
while (sumab[i] + sumcd[start] == 0 && start < k)
{
count++;
start++;
}
}
cout << count << endl;
return 0;
}
TT 的神秘礼物
题意:
任务内容是,给定一个 N 个数的数组 cat[i],并用这个数组生成一个新数组 ans[i]。新数组定义为对于任意的 i, j 且 i != j,均有 ans[] = abs(cat[i] - cat[j]),1 <= i < j <= N。试求出这个新数组的中位数,中位数即为排序之后 (len+1)/2 位置对应的数字,’/’ 为下取整。
Input:
多组输入,每次输入一个 N,表示有 N 个数,之后输入一个长度为 N 的序列 cat, cat[i] <= 1e9 , 3 <= n <= 1e5.
output:
输出新数组 ans 的中位数.
思路:
将cat数组从小到大排列,中位数的名次是已知的(n*(n-1)/2+1)/2 对每一次二分得到的名次与中位数的名次比较,如果名次小于中位数,就代表这个数比中位数小,否则就比中位数大。然后目标是求这个二分值P,再求名次。ans中的数组元素是cat[j]-cat[i](i<j),然后就变成了就是求满足cat[j]-cat[i]<=P的所有二元组对数。通过移项,得到cat[j]<=P+cat[i],接着就枚举i满足j的个数,就得到了名次。然后这个名次是利用二分查找,满足最后一个cat[j]>=P+cat[i]的位置,就得到j的最大值;名次等于每一次j的最大值减去i的累加和。求出名次再和中位数名次比较,直到找到中位数就行了。
总结:
1.这道题比较绕,由于要进行两次二分,进行首轮二分的时候想通过网上找找有没有比较实用的二分函数:于是我找到了这个
(1)、binary_search(beg,end,val)
返回一个bool变量,以二分法检索的方式在[beg,end]之间查找val,找到返回true,找不到返回false。
(2)、lower_bound(beg,end,val)
返回一个迭代器,指向非递减序列[first, last)中的第一个大于等于(>=)val的位置。
(3)、upper_bound(beg,end,val)
返回一个迭代器,指向非递减序列[first, last)中的第一个大于 (>) val的位置。
原文链接:https://blog.csdn.net/xzymmd/article/details/83902281
这些都是利用二分来找到位置。通过这些函数的原理让我知道了如何进行二次二分。
2.由于time limit,一开始用的cin导致多次TE,找了好几遍以为是我二分的原因,修改了一下还是TE,然后尝试了一下scanf就通过了。
后来请教了朋友学到了ios::sync_with_stdio(false)可以提高cin的效率。
总之这道题让我受益颇多,学到了很多实用的知识。
代码:
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int main()
{
int n;
int start, end, mid, median, rank,mid1,ans,ans1;
while (cin >> n)
{
int cat[100002];
for (int i = 0; i < n; i++)
{
scanf("%d",&cat[i]);
}
sort(cat, cat + n);
ans=-1;
start = 0; //绝对值最小只为0
end = cat[n - 1] - cat[0]; //最大的数
median = (1 + n * (n - 1) / 2) / 2; //中位数所在位置
while (start <= end)
{
mid = (start + end) / 2;
rank = 0; //初始化位置
for (int i = 0; i < n; i++)
{
int temp=cat[i] + mid;
int l = 0, r = n-1;
ans1=-1;
while (l <= r)
{
mid1 = (l + r)/2;
if (cat[mid1] <= temp)
{
ans1=mid1;
l=mid1+1;
}
else r = mid1 - 1;
}
if(ans1!=-1)
rank+=ans1-i;
}
if(rank<median)
{
start=mid+1;
}
else
{
ans=mid;
end=mid-1;
}
}
cout << ans << endl;
}
return 0;
}