24年春招/暑期实习-阿里-笔试真题卷(4)

第一题:周赛最高分

在线测评链接:http://121.196.235.151/p/P1153

题目描述

ak机是leetcode周赛的忠实玩家,为了防止周赛掉分和刷新knight的分数线,他总共创建了 n n n个账号,每个账号的分数分别为 a i a_i ai,现在我们记录了他 m m m次的比赛记录,ak机每次都会使用分数最低的账号参赛,请问ak机每次参赛后,他的所有账号的最大得分是多少

输入描述

输入包含三行

第一行两个正整数 n , m ( l ≤ n , m ≤ 1 0 5 ) n,m(l≤n,m≤10^5) n,m(ln,m105),分别表示ak机的账号个数和ak机新参加的比赛记录数。

第二行 n n n个整数 a i ( 0 ≤ a i ≤ 1 0 9 ) a_i(0\le a_i\le 10^9) ai(0ai109),表示ak机每个账号目前的分数。

第三行 m m m 个整数 b j ( 0 ≤ b j ≤ 1 0 9 ) b_j(0\le b_j\le 10^9) bj(0bj109),分别表示ak机每次比赛后,分数的变化值

(例如如果ak机使用分数为 x x x的账号参赛,那么他在参加完第 j j j场数的变化值。比赛后,该账号分数会变为 x + b j x+b_j x+bj。)

输出描述

输出包含 m m m行,每行一个整数,表示ak机参与完第 j j j场比赛后,他的所有账号的最大得分

样例

输入

5 4
1145 1200 1300 1500 1600
10 270 450 500

输出

1600
1600
1650
1800

解释

第一次和第二次询问都加在1145上,数组变成 [1425,1200,1300,1500,1600],第三次和第四次分别加在1200和1300上

题解:模拟 小根堆

由于每一次都是数组中的最小值和 b j b_j bj操作,因此我们可以维护一个小根堆来存储他的动态分数,那么每次操作只需要弹出小根堆的堆顶元素,并将值加上 b j b_j bj,然后将其与数组中最大值取 m a x max max即可。

本题C++和Java选手需要注意:数据范围较大,需要开long long

C++

#include<bits/stdc++.h>
using namespace std;
const int N=1E5+10;
long long n,m,a[N],b[N];
int main(){
    cin>>n>>m;
    priority_queue<long long,vector<long long>,greater<long long>>heap;  //小根堆
    long long res=0;
    for(int i=0;i<n;i++){
        cin>>a[i];
        heap.push(a[i]);
        res=max(res,a[i]);
    }
    for(int i=0;i<m;i++){
        cin>>b[i];
        long long score=heap.top();heap.pop();
        res=max(res,score+b[i]);
        heap.push(score+b[i]);
        cout<<res<<endl;
        
    }
    return 0;
}

Java

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int m = scanner.nextInt();
        PriorityQueue<Long> heap = new PriorityQueue<>();  // 小根堆
        long res = 0;
        long[] a = new long[n];
        for(int i = 0; i < n; i++) {
            a[i] = scanner.nextLong();
            heap.add(a[i]);
            res = Math.max(res, a[i]);
        }
        long[] b = new long[m];
        for(int i = 0; i < m; i++) {
            b[i] = scanner.nextLong();
            long score = heap.poll();
            res = Math.max(res, score + b[i]);
            heap.add(score + b[i]);
            System.out.println(res);
        }
    }
}

python

import heapq

n, m = map(int, input().split())
heap = []  # 小根堆
res = 0
a = list(map(int, input().split()))
for i in range(n):
    heapq.heappush(heap, a[i])
    res = max(res, a[i])
b = list(map(int, input().split()))
for i in range(m):
    score = heapq.heappop(heap)
    res = max(res, score + b[i])
    heapq.heappush(heap, score + b[i])
    print(res)

第二题:数组的删除方案

在线测评链接:http://121.196.235.151/p/P1154

题目描述

ak机有一个长度为 n n n的数组 a a a,他想要使得数组 a a a有序(单调不降),他必须选择一段区间 [ l , r ] ( 1 ≤ l , r ≤ n ) [l,r](1\le l,r\le n) [l,r](1l,rn),将数组的这一段删除,其他的部分(如果存在的话)就按顺序拼在一起。
现在他想知道有多少种不同的选择区间的方案。

输入描述

第一行一个正整数 n ( 1 ≤ n ≤ 2 × 1 0 5 ) n(1\le n\le 2\times 10^5) n(1n2×105),表示数组的长度。

第二行 n n n个正整数 a i ( 1 ≤ a i ≤ 1 0 9 ) a_i(1\le a_i\le 10^9) ai(1ai109),表示数组 a a a

输出描述

输出一行一个正整数表示答案。

样例

输入

3
1 2 3

输出

6

说明

可以选择:
[1, 1], [2, 2], [3, 3], [1, 2], [2, 3], [1, 3]
这六个区间

思路:双指针+二分查找

首先,我们考虑一种极端情况,就是整个数组本来就是一个单调非降数组,比如 [ 1 , 2 , 3 ] [1,2,3] [1,2,3],那么删除任意一个子区间都可以,因此对应的方案数记为子区间个数,等于 n × ( n + 1 ) 2 \frac{n\times (n+1)}{2} 2n×(n+1)

对于一般的情况,比如 [ 1 , 3 , 4 , 3 , 5 , 2 , 2 , 5 ] [1,3,4,3,5,2,2,5] [1,3,4,3,5,2,2,5] 左侧则为 [ 1 , 3 , 4 ] [1,3,4] [1,3,4],右侧则为 [ 2 , 2 , 5 ] [2,2,5] [2,2,5]

可以证明,中间的 [ 3 , 5 ] [3,5] [3,5]总会出现在删除方案中,也就是选择的 [ l , r ] [l,r] [l,r]区间一定包含 [ 3 , 5 ] [3,5] [3,5]

我们首先找到左侧的单调最长子数组 l e f t left left和右侧的单调最长子数组 r i g h t right right

有以下几种删除方式

  • 第一种删除方式:选择一侧,删除另一侧的全部数字,比如选择左侧
保留[1,3,4]对应删除[3,5,...]
保留[1,3]  对应删除[4,3,5...]
保留[1]    对应删除[3,4,3,5...]

另一侧同理可得,选择一侧的删除方案数为 l e f t . s i z e ( ) + r i g h t . s i z e ( ) left.size() + right.size() left.size()+right.size()

  • 第二种删除方式:两侧中都保留若干个数字,删除中间段,比如
左侧保留[1],右侧可以保留[2,2,5]、[2,5]、[5],则相应删除[3,4,3,5]、[3,4,3,5,2]..
左侧保留[1,3],右侧则可以保留[5]

由于是递增子数组,因此可以使用二分查找到另一侧保留的位置,进而得出有几种删除方案。

C++

#include<bits/stdc++.h>
using namespace std;
const int N=1E5+10;
int n,a[N];
int main(){
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i];
    a[0]=0;a[n+1]=INT_MAX;
    int i=1,j=n;
    while(i<=n&&a[i]>=a[i-1]){  //找到左侧的最长上升子数组
        i++;
    }
    while(j>=1&&a[j]<=a[j+1]){   //找到右侧的最长上升子数组
        j--;
    }
    i--;j++;  //定位到最后一个上升的位置
    if(i>j){  //说明整个数组就是一个非递减数组,删除任意区间都满足条件
        long long res=1ll*n*(n+1)/2;
        cout<<res<<endl;
    }
    else{
        long long res=i+n-j+1;  //删除左侧的递增数组or右侧的递增数组
        res++;   // 特殊情况,数组可以全部删除,这里题目中没有讲,只能在样例中看到
        for(int pos=1;pos<=i;pos++){  //枚举左侧上升子数组的每一个位置
            int l=j,r=n;
            while(l<r){
                int mid=l+r>>1;
                if(a[mid]>=a[pos])r=mid;
                else l=mid+1;
            }
            if(a[l]>=a[pos]){  //子数组[1,pos]与[l,l],[l,l+1]...可以合并在一起,总共有n-l+1种方案
                res+=n-l+1;
            }
        }
        cout<<res<<endl;
    }
    
    return 0;
}

Java

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int[] a = new int[n + 2];
        a[0] = 0;
        a[n + 1] = Integer.MAX_VALUE;
        for (int i = 1; i <= n; i++) {
            a[i] = scanner.nextInt();
        }

        int i = 1, j = n;
        while (i <= n && a[i] >= a[i - 1]) {  // 找到左侧的最长上升子数组
            i++;
        }
        while (j >= 1 && a[j] <= a[j + 1]) {  // 找到右侧的最长上升子数组
            j--;
        }
        i--;
        j++;  // 定位到最后一个上升的位置

        if (i > j) {  // 说明整个数组就是一个非递减数组,删除任意区间都满足条件
            long res = 1L * n * (n + 1) / 2;
            System.out.println(res);
        } else {
            long res = i + n - j + 1;  // 删除左侧的递增数组or右侧的递增数组
            res++;   // 特殊情况,数组可以全部删除,这里题目中没有讲,只能在样例中看到
            for (int pos = 1; pos <= i; pos++) {  // 枚举左侧上升子数组的每一个位置
                int l = j, r = n;
                while (l < r) {
                    int mid = (l + r) / 2;
                    if (a[mid] >= a[pos]) {
                        r = mid;
                    } else {
                        l = mid + 1;
                    }
                }
                if (a[l] >= a[pos]) {  // 子数组[1,pos]与[l,l],[l,l+1]...可以合并在一起,总共有n-l+1种方案
                    res += n - l + 1;
                }
            }
            System.out.println(res);
        }
    }
}

Python

n = int(input())
a = list(map(int, input().split()))
a.insert(0, 0)
a.append(2 * 10 ** 9)

i = 1
j = n
while i <= n and a[i] >= a[i - 1]:  # 找到左侧的最长上升子数组
    i += 1
while j >= 1 and a[j] <= a[j + 1]:  # 找到右侧的最长上升子数组
    j -= 1
i -= 1
j += 1  # 定位到最后一个上升的位置

if i > j:  # 说明整个数组就是一个非递减数组,删除任意区间都满足条件
    res = n * (n + 1) // 2
    print(res)
else:
    res = i + n - j + 1  # 删除左侧的递增数组or右侧的递增数组
    res += 1  # 特殊情况,数组可以全部删除,这里题目中没有讲,只能在样例中看到
    for pos in range(1, i + 1):  # 枚举左侧上升子数组的每一个位置
        l = j
        r = n
        while l < r:
            mid = (l + r) // 2
            if a[mid] >= a[pos]:
                r = mid
            else:
                l = mid + 1
        if a[l] >= a[pos]:  # 子数组[1,pos]与[l,l],[l,l+1]...可以合并在一起,总共有n-l+1种方案
            res += n - l + 1
    print(res)

第三题:最长的R区间

在线测评链接:http://121.196.235.151/p/P1028

题目描述

给定长度为 n n n的一个字符串(仅由 R R R W W W组成),你需要把字符串中的所有字符都修改为 R R R,每一次修改只能将连续的 k k k个字符修改成 R R R,最大修改次数为 m m m,求 k k k的最小值

输入描述

第一行输入两个正整数 n , m ( 1 ≤ n ≤ 2 × 1 0 5 ) n,m(1\le n\le 2\times 10^5) n,m(1n2×105)

第二行输入长度为 n n n的字符串

输出描述

输出一个整数,表示 k k k的最小值

样例

输入

5 2
WRWWWR

输出

3

思路:二分答案

首先可知, k k k的值越大,每次修改的区间长度就越大,那么对应的修改次数就越少,就越容易满足修改次数 ≤ m \le m m的条件

反之,则越不容易满足题目条件,因此是具有二段性的

因此我们可以对答案使用二分查找来枚举,当我们枚举到 k k k的值为 x x x

我们使用贪心的思想进行check,当我们在位置 i i i遇到 W W W字符的时候,我们就使用一次修改次数,然后就可以把整个区间 [ i , i + x − 1 ] [i,i+x-1] [i,i+x1]都变成 R R R,如果最终修改完所有字符串,消耗的次数 ≤ m \le m m,则说明当前答案满足条件,否则不满足条件,我们按照二分查找的方式对区间进行缩小

  • 如果最终修改完字符串,消耗的次数 c n t ≤ m cnt\le m cntm,则说明当前答案满足条件,可以将区间缩小至 [ l , x ] [l,x] [l,x]
  • 如果最终修改完字符串,消耗的次数 c n t > m cnt> m cnt>m,则说明当前答案不满足条件,可以将区间缩小至 [ x + 1 , r ] [x+1,r] [x+1,r]

C++

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m;
string s;
bool check(int x){
    int cnt=0;
    for(int i=0;i<n;i++){
        if(s[i]=='W'){
            cnt++;
            i+=x-1;
        }
    }
    return cnt<=m;
}
int main()
{
    cin>>n>>m;
    cin>>s;
    int l=1,r=1e9;
    while(l<r){
        int mid=l+r>>1;
        if(check(mid))r=mid;
        else l=mid+1;
    }
    cout<<r<<endl;
	return 0;
}

Java

import java.util.*;

public class Main {
    static int n, m;
    static String s;

    static boolean check(int x) {
        int cnt = 0;
        for (int i = 0; i < n; i++) {
            if (s.charAt(i) == 'W') {
                cnt++;
                i += x - 1;
            }
        }
        return cnt <= m;
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        n = scanner.nextInt();
        m = scanner.nextInt();
        s = scanner.next();
        int l = 1, r = (int)1e9;
        while (l < r) {
            int mid = l + (r - l) / 2;
            if (check(mid)) r = mid;
            else l = mid + 1;
        }
        System.out.println(r);
    }
}

Python

def check(x):
    cnt = 0
    i = 0
    while i < n:
        if s[i] == 'W':
            cnt += 1
            i += x
        else:
            i += 1
    return cnt <= m

n, m = map(int, input().split())
s = input()
l, r = 1, int(1e9)
while l < r:
    mid = l + (r - l) // 2
    if check(mid):
        r = mid
    else:
        l = mid + 1
print(r)
  • 14
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值