2020阿里实习生招聘笔试题

题目1

小强有n个养鸡场,第i个养鸡场初始有a[i]只小鸡,小强的每个养鸡场每天早上都会增加k只小鸡,到了下午小强会把鸡最多的鸡场卖掉一半鸡,那么小强想知道m天后他所有养鸡场一共有几只鸡。
第一行输入三个正整数n,m,k;
第二行输入n个正整数a[i]表示养鸡场鸡数量。
1<=n<=100000, 1<=m<=100000
1<=k<=10000, 1<=a[i]<=100000
输出一个整数表示答案。
样例:
输入
3 3 100
100 200 400
输出
925

分析

所有的鸡场每天会增加k只鸡,我们不妨把鸡场分成两间屋子,一间屋子装原来的鸡a[i],一间屋子装新增的鸡k,每个养鸡场新增的鸡的数量都是一样的,假如现在是第1天,第now号养鸡场有最多鸡,那么需要卖掉(a[now]+k)/2只鸡,我们假设只卖第一间屋子的鸡,那么两个屋子的鸡数量变成了a[now]-(a[now]+k)/2和k,而其他鸡场两个屋子的鸡数量是a[i]和k,比较大小的时候我们可以不需要比较第二间屋子的鸡,因为所有鸡场第二间屋子的鸡数量都一样,按照这个思路,每天只需要取第一间屋子鸡最多的那个养鸡场,然后a[now] = a[now]-(a[now] + x*k)/2,这里x是指第x天,第二间屋子有x*k只鸡了,为了方便取第一间屋子最大的鸡数量,我们把原鸡场的鸡数量放进一个优先队列当中,每天进行一次pop最大鸡数量和push卖完剩下的鸡数量。鸡的总数为所有鸡场第一间屋子+第二间屋子,这里第二间屋子第m天总共有n*m*k只鸡,第一间屋子总共有的就是队列里面的值相加。

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
 
int main()
{
    int n, m, k;
    cin >> n >> m >> k;
    long long chick;
    priority_queue<long long> que;
    for (int i = 0; i < n; i++){
            cin >> chick;
            que.push(chick);
    }
    for (int i = 1; i <= m; i++){
        long long now = que.top();
        que.pop();
        now -= (now+i*k)/2;
        que.push(now);
    }
    long long sum = 0;
    while (!que.empty())
    {
        sum += que.top();
        que.pop();
    }
    cout << sum + n*m*k << endl;
    return 0;
}

题目二

小强得到了一个长度为n的序列,他想随机选择这个序列的一个连续子序列,并求出这个序列的最大值,请你告诉他这个最大值的期望是多少?
输入第一行n表示序列长度,1<=n<=10^6;
第二行为n个正整数,每个数字不超过10^5。
输出这个最大值的期望,保留六位小数。
样例:
输入
3
1 2 3
输出
2.333333

分析

期望是指所有连续子序列的最大值相加除以连续子序列数量,一个序列有多少连续子序列呢?假如序列长度为n,那么长度为1的连续子序列有n个,长度为2的连续子序列有n-1个…所以一共有n*(n+1)/2个连续子序列,那我们把所有的连续子序列求出来然后把最大值相加?显然很不合理,复杂度爆表,换一个思路,如果以当前这个数字为最大值,哪些序列是满足的?先往左找到左边界再往右找到右边界,例如4 1 2 3 2 1 4如果以3为最大值,满足的范围是1 2 3 2 1,满足的子序列是1 2 3,1 2 3 2,1 2 3 2 1,2 3,2 3 2,2 3 2 1,3,3 2,3 2 1共九个,实际可以用两个变量来计数,一个记左边有几个数,一个记右边有几个数,上面的例子左边是1 2 3三个数,右边是3 2 1三个数,一共有3*3=9种排列方式。这样我们遍历一遍就可以得到期望的分子,再除以数量即可。

代码

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
 
int main()
{
    int n; cin >> n;
    long long num[n+1];
    for (int i = 0; i < n; i++) cin>>num[i];
    long long sum = 0;
    for (int i = 0; i < n; i++){
        int L = 1, R = 1;
        while (i-L >= 0 && num[i-L] <= num[i]) L++;
        while (i+R < n && num[i+R] <= num[i]) R++;
        sum += num[i]*L*R;
    }
    long long m = n*(n+1)/2;
    cout << 1.0*sum/m << endl;
    return 0;
}

但是存在一些问题,比如 3 1 3,在计算第一个3的时候会用到313序列,第二个也会用到,重复计算了,那么我们把判断大小的等于号只取左边不取右边,也就是

while (i-L >= 0 && num[i-L] <= num[i]) L++;
while (i+R < n && num[i+R] < num[i]) R++;

这样就避免了重复。
复杂度可能还是不足,但是笔试一小时的情况下也只能处理成这样了。

补充

最近正好学习了单调栈,对于求解这种左右边界十分方便,所以重新修改了一下代码,复杂度降低。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <stack>
using namespace std;

int n, L[1000100], R[1000100];
long long num[1000100];
int main()
{
    cin >> n;
    for (int i = 0; i < n; i++)
        cin>>num[i];
    stack<int> sta;
    sta.push(-1);
    for (int i = 0; i < n; i++)
    {
        while (sta.top()!=-1 && num[sta.top()] <= num[i])
        {
            sta.pop();
        }
        L[i] = sta.top() + 1;
        sta.push(i);
    }
    while (!sta.empty()) sta.pop();
    sta.push(n);
    for (int i = n-1; i >= 0; i--){
        while (sta.top()!=n && num[sta.top()] < num[i])
            sta.pop();
        R[i] = sta.top()-1;
        sta.push(i);
    }
    long long sum = 0;
    for (int i = 0; i < n; i++)
        sum += num[i]*(i - L[i]+1)*(R[i]-i+1);
    double ans = 2.0*sum /n /(n+1);
    printf("%.6f\n", ans);
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值