A.ddl的恐惧(贪心)
题意
有n([1,1000])个作业,每个左右有相应的ddl,如果未在ddl之前做完作业,将会扣除一定的分数(每个作业消耗相同的一个单位时间)
求最少会被扣除多少分数
样例
样例输入:
第一行一个数字T 代表数据组数
每一组样例的第一行数字 n 代表作业数量
下一行n个数字ai 代表第i个作业的ddl
再下一行n个数字bi 代表第i个作业的分值
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
样例输出:
一组数据输出一个答案ans 表示最少被扣掉的分数 单独占一行
0
3
5
思路
1.将每个任务按照ddl的大小降序排列,即ddl大的在前面
2.将时间t初始化为最大的ddl(t再大也没用,因为没有作业需要做) 从后向前遍历时间t
3.在每个时间t里,从k(初始化为1)到n遍历task数组,将其中的score值放入到大根堆中 直到task[i]的ddl不等于t 并且弹出大根堆的堆顶元素 并使 t-- 代表时刻t完成了分值为q.top()的任务(注意for循环中k每次也要自加 因为k是每一次遍历task数组的起始下标)
4.处理完t后将堆内所有元素求和,即为最少扣除的分数
总结
1.由于每个作业仅占1时间单位,对其他作业的调度影响很小,所以我们要优先考虑分数高的作业
2.对于某一作业,我们期望它能够尽可能晚的完成,这样可以把前面的时间让给ddl靠前的作业,于是t逆序遍历
3.由于需要维护最大值,故考虑使用大根堆优化性能
代码
#include<iostream>
#include<string.h>
#include<math.h>
#include<algorithm>
#include<queue>
#define MAXN 1010
using namespace std;
struct task
{
int score;
int ddl;
bool operator <(const task & x ) const{
if(ddl!=x.ddl) return ddl>x.ddl;
else return score>x.score;
}
}a[MAXN];
bool cmp(task a,task b)
{
if(a.ddl!=b.ddl) return a.ddl>b.ddl;
}
int main()
{
int T,n;
cin>>T;
while(T--)
{
int ans=0;
priority_queue< int > q;
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i].ddl;
for(int i=1;i<=n;i++) cin>>a[i].score;
sort(a+1,a+n+1,cmp);
int t=a[1].ddl;//我们只需要考虑时间[1,t]内
int k=1;
while(t!=0)
{
for(int i=k;i<=n&&a[i].ddl==t;i++,k++) q.push(a[i].score);
if(!q.empty()) q.pop();//每个时刻内完成一个任务(如果有任务)
t--;
}
while(!q.empty())
{
ans+=q.top();//剩下的任务是没被做完的
q.pop();
}
cout<<ans<<endl;
}
return 0;
}
B.四个数列(二分)
题意
有四个含n([1,4000])个元素的数列a b c d,从4个数列中各取一个数,问有多少种取法使得4个数的和为0(如果某个数列中有相同大小的元素,则当做两个不同元素处理)
样例
样例输入:
第一行 一个数字n
接下来n行 每行4个数字 代表ai bi ci di(ai,bi,ci,di<=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
样例输出:
输出一个数字 代表方案总数
5
思路
1.枚举所有a与b的组合,将其和的所有可能存入vector
2.将vector 从小到大排序
3.枚举c与d的所有组合,在vector二分查找c与d和的相反数。即先找到最小下标,再找到最大下标 再相减并加1
总结
1.n=4000的数据代表我们只能选用 n ^ 2 或是 n ^ 2 log n 的方案 而2^28的数据大小使得我们无法使用桶排序这种简单易写的方法,所以我们只能存入容器中再进行查找。
2.在有序数列中查找元素,二分查找无疑是最优的算法 ^ ^
代码
#include<iostream>
#include<string.h>
#include<math.h>
#include<algorithm>
#include<queue>
#include<vector>
#define MAXN 5010
using namespace std;
int a[MAXN],b[MAXN],c[MAXN],d[MAXN];
int n,ans;
vector<int> v;
int check(int x)
{
int l=0,r=v.size()-1,mid,min_index=-1,max_index=-1;
while(l<=r)//找最小下标
{
mid=(l+r)/2;
if(v[mid]>x) r=mid-1;
else if(v[mid]<x) l=mid+1;
else if(v[mid]==x) {r=mid-1;min_index=mid;}
}
l=0;r=v.size()-1;
while(l<=r)//找最大下标
{
mid=(l+r)/2;
if(v[mid]>x) r=mid-1;
else if(v[mid]<x) l=mid+1;
else if(v[mid]==x) {l=mid+1;max_index=mid;}
}
if(min_index==-1) return 0;
else return max_index-min_index+1;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i]>>b[i]>>c[i]>>d[i];
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++) v.push_back(a[i]+b[j]);
sort(v.begin(),v.end());
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++) ans+=check(-c[i]-d[j]);
cout<<ans<<endl;
system("pause");
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 位置对应的数字,’/’ 为下取整。
样例
样例输入:
多组输入 每次输入一个 N,表示有 N 个数,之后输入一个长度为 N 的序列 cat, cat[i] <= 1e9 , 3 <= n <= 1e5
4
1 3 2 4
3
1 10 2
样例输出:
输出新数组 ans 的中位数
1
8
思路
1.我们可以先将序列排序 对于ai,aj 我们规定j>i 这样就把绝对值号给拆开了
2.由于对于一个新数列中的数,他的次序必然会随着数值的增大而增大,于是就有了二分答案的可能
3.故我们可以先二分答案,假设答案是mid,然后计算mid在新序列中的次序,如果小于中位数就在右边的区间寻找,否则就在右边的区间寻找,同时更新ans记录(注意 中位数的次序并不一定正好是n(n-1)/2的位置 因为可能含重复元素 如 1 1 2 2 2 3 2的次序=5 !=3 但是2是中位数 故需要吧等号归到>里面*)
4.在查询某个答案的次序的时候 可以通过普通的二分 即根据aj-ai<=x 我们可以通过枚举i 然后二分寻找aj<=ai+x的最大下标 用它减去i 并求和就是当前答案的次序
总结
1.本道题的n为1e5,如果通过暴力的枚举所有i,j的组合,并且排序找出他们的中位数,时间复杂度在O(n ^ 2logn ^ 2) 级别 ,显然是不合适的
2.故本题采用了先二分答案 再普通二分确定当前答案的次序的方法 大大降低了程序的时间复杂度
3.二分答案这种方法具有非常广泛的应用,但是我们不一定能看出来这道题能不能用二分答案做,于是问题的关键在于 看出能否有二分答案做
代码
#include<iostream>
#include<stdio.h>
#include<math.h>
#include<string.h>
#include<algorithm>
#define MAXN 200010
int cat[MAXN];
int n;
using namespace std;
int func(int x,int i)//该函数用于计算有多少个 cat[j] <=x 即 j的最大值
{
int l=1,r=n,mid,ans=-1;
while(l<=r)
{
mid=(l+r)>>1;
if(cat[mid]<=x) {ans=mid;l=mid+1;}
else if(cat[mid]>x) r=mid-1;
}
if(ans==-1) return 0;
else return ans-i;
}
int main()
{
// freopen("input.txt","r",stdin);
//freopen("output.txt","w",stdout);
while(scanf("%d", &n) != EOF)
{
for(int i=1;i<=n;i++) scanf("%d",&cat[i]);
sort(cat+1,cat+n+1);//为了拆除绝对值号 约定让 aj-ai 其中j>=i
//基本思路为二分答案 假设p是答案 计算 aj-ai<=p 的所有序偶 即 aj<=ai+p
int l=0,r=cat[n]-cat[1],mid;//中位数的取值在 0 到 最大减最小之间
//中位数为排序之后 (len+1)/2位置的数字 故 <=zws 的数有(len+1)/2个
int len=n*(n-1)/2,ans=0;
int zws_rank=(len+1)/2;//长度为 1+2+3+···+n-1 = n(n-1)/2
while(l<=r)//此处为二分答案的框架 计算 mid 排在第几位
{
mid=(l+r)>>1;
int sum=0;
for(int i=1;i<=n;i++) sum+=func(cat[i]+mid,i);
if(sum>=zws_rank) {ans=mid;r=mid-1;}
else if(sum<zws_rank) l=mid+1;
}
cout<<ans<<endl;
}
return 0;
}
来源:CSDN
作者:林深见海
链接:https://blog.csdn.net/linshen_jianhai/article/details/104850877