[贪心/二分] Exercise Week4 A+B+C题

≯℡__Kan透↙ 提交于 2020-03-17 01:56:22

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;
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!