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

第一题:打游戏

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

题目描述

ak机正在玩一个游戏,游戏中nnn个怪物,血量上限分别为hih_ihi,初始时所有怪物的血量都等于它们的血量上限,当怪物的血量小于或等于0时,怪物将会死亡。
ak机有两个技能
第一个技能为旋风斩,消耗一点法力,对所有怪物造成1点伤害
第二个技能为斩杀,消耗两点法力,杀死一个已受伤的怪物(当前怪物血量小于怪物的血量上限)。
两个技能都没有使用次数限制,ak机想知道她最少需要消耗多少点法力才能杀死所有怪物。

输入描述

第一行输入一个整数 n(2≤n≤105)n(2 ≤n≤ 10^5)n(2n105)表示怪物数量。

第二行输入 nnn个整数hi(1≤hi≤109)h_i(1 ≤ h_i≤ 10^9)hi(1hi109)表示怪物的血量上限

输出描述

输出一个整数表示答案,

样例

输入

3
1 1 4

输出

3

说明

首先使用旋风斩,怪物的血量变成:0 0 3,第1、2个怪物死亡。
再对第三个怪物使用斩杀,第3个怪物死亡。消耗的法力值为1+2=3。

思路:模拟 枚举

首先,根据题目描述,一技能一定要至少释放一次(这是释放二技能的前提)

首先一种暴力的思想是可以直接枚举使用一技能的次数cntcntcnt

但是,由于怪物的血量范围是1≤hi≤1091\le h_i\le 10^91hi109,因此我们肯定不能直接枚举cntcntcnt

由于只有n(1≤n≤105)n(1\le n\le 10^5)n(1n105)个怪物,因此我们可以对怪物的血量从小到大进行排序,然后枚举第iii个怪物被一技能直接干掉

iii个怪物被一技能直接干掉的一技能使用次数即为w[i]w[i]w[i],显然前面i−1i-1i1个怪物也会被干掉

对于后面的怪物,都是用第二个技能,即为第iii个怪物的最小花费

因此第iii个怪物的最小花费即为w[i]+(n−i−1)×2w[i]+(n-i-1)\times 2w[i]+(ni1)×2

C++

#include<bits/stdc++.h>
using namespace std;
const int N=1E5+10;
int w[N],n;
int main(){
    cin>>n;
    for(int i=0;i<n;i++)cin>>w[i];
    sort(w,w+n);
    int res=1e9;
    for(int i=0;i<n;i++){
        res=min(res,w[i]+(n-i-1)*2);
    }
    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();
        Integer[] w = new Integer[n];
        for(int i = 0; i < n; i++) {
            w[i] = scanner.nextInt();
        }
        Arrays.sort(w);
        int res = (int)1e9;
        for(int i = 0; i < n; i++) {
            res = Math.min(res, w[i] + (n - i - 1) * 2);
        }
        System.out.println(res);
    }
}

Python

n = int(input())
w = list(map(int, input().split()))
w.sort()
res = int(1e9)
for i in range(n):
    res = min(res, w[i] + (n - i - 1) * 2)
print(res)

第二题:互补数组

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

题目描述

ak机定义两个数组是互补的,当且仅当数组每一个位置的数字之和都相同。

ak机有两个长度为nnn的数组,分别是aaabbb,她想知道有多少个子序列对应的数组是互补的。

输入描述

第一行输入一个整数 n(1≤n≤105)n(1 ≤n≤ 10^5)n(1n105)表示数组长度

第二行输入 nnn个整数表示数组a(1≤ai≤109)a(1≤ a_i≤ 10^9)a(1ai109)

第三行输入 mmm个整数表示数组b(1≤bi≤109)b(1 ≤b_i\le 10^9)b(1bi109)

输出描述

输出一个整数,由于这个整数可能很大,因此你需要输出这个整数对109+710^9 +7109+7取模后的结果。

样例

输入

3
1 2 3
3 2 1

输出

7

说明

子序列:1,2,3,12,13,23,123,都满足条件
这里的1,2,3指的是数组下标的位置(从1开始)

思路:哈希表+贡献法计数+快速幂

首先解释一下样例1,因为任意一个位置iiia[i]+b[i]a[i]+b[i]a[i]+b[i]都为4,因此有3个4,我们可以考虑选或者不选(至少要选1个位置)

因此,总的情况就是23−1=72^3-1=7231=7

我们可以使用一个哈希表来统计所有位置a[i]+b[i]a[i]+b[i]a[i]+b[i]出现的次数,对于某一个权值www,其出现的次数为cntcntcnt

那么它对答案的贡献就是2cnt−12^{cnt}-12cnt1

注意由于答案过大,需要对109+710^9+7109+7取模,因此这里我们可以使用快速幂来快速地求出来2k2^k2k,更新答案

本题数据范围较小,因此这里就不给大家使用快速幂的做法了,我们使用另一种做法,维护一个数组来预处理2k2^k2k的结果

C++

#include<bits/stdc++.h>
using namespace std;
const int N=1E5+10,mod=1e9+7;
int a[N],b[N],n,res;
int main(){
    cin>>n;
    for(int i=0;i<n;i++)cin>>a[i];
    for(int i=0;i<n;i++)cin>>b[i];
    unordered_map<int,int>cnts;
    for(int i=0;i<n;i++){
        cnts[a[i]+b[i]]++;
    }
    vector<int>twos(n+1,1);
    for(int i=1;i<=n;i++){
        twos[i]=1ll*twos[i-1]*2%mod;
    }
    for(auto &[u,v]:cnts){
        res=(res+1ll*(twos[v]-1+mod)%mod)%mod;
    }
    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[] a = new int[n];
        int[] b = new int[n];
        for(int i = 0; i < n; i++) {
            a[i] = scanner.nextInt();
        }
        for(int i = 0; i < n; i++) {
            b[i] = scanner.nextInt();
        }
        HashMap<Integer, Integer> cnts = new HashMap<>();
        for(int i = 0; i < n; i++) {
            cnts.put(a[i] + b[i], cnts.getOrDefault(a[i] + b[i], 0) + 1);
        }
        int[] twos = new int[n + 1];
        twos[0] = 1;
        int mod = (int)1e9 + 7;
        for(int i = 1; i <= n; i++) {
            twos[i] = (int)(((long)twos[i - 1] * 2) % mod);
        }
        int res = 0;
        for(int v : cnts.values()) {
            res = (res + ((twos[v] - 1 + mod) % mod)) % mod;
        }
        System.out.println(res);
    }
}

Python

n = int(input())
a = list(map(int, input().split()))
b = list(map(int, input().split()))
cnts = {}
for i in range(n):
    cnts[a[i] + b[i]] = cnts.get(a[i] + b[i], 0) + 1
twos = [1] * (n + 1)
mod = int(1e9) + 7
for i in range(1, n + 1):
    twos[i] = twos[i - 1] * 2 % mod
res = 0
for v in cnts.values():
    res = (res + (twos[v] - 1 + mod) % mod) % mod
print(res)

第三题:数的最大权值

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

题目描述

小苯定义一个数字的权值为:该数字的因子个数。

小苯现在拿到了一个正整数,他希望将:分解为若干不等于1的数字(也可以不做分解),使得所有分解出的正整数乘积等于xxx,且所有数字的权值之和尽可能大,你能帮帮他求出最大的权值吗。

输入描述

输入包含T十1T 十1T1行。
第一行一个正整数 T(1≤T≤104)T(1\le T\le 10^4)T(1T104),表示数据组数。

接下来TTT行,每行一个正整数x(2≤x≤105)x(2 \le x \le10^5)x(2x105),表示每组数据中小苯询问的数字xxx

输出描述

输出包含TTT 行,每行一个正整数表示每组测试数据的最大权值和

样例

输入

3
2
10
123

输出

2
4
4

说明

第一个测试数据中,无法分解,直接取2的权值为 2.
第二个测试数据中,将 10 分解为 2x5,权值和为 4。

思路:质因数分解+分类讨论

本题有两种情况

  • 情况1:xxx仅有一个质因子aaa,即x=akx=a^kx=ak

考虑两种情况,拆分和不拆分

拆分:对于每一个质因子,它的因子个数都是2(1和它本身),因此对应的答案为2k2k2k

不拆分:对应因子个数为k+1k+1k+1(有1,a,a2,...,ak1,a,a^2,...,a^k1,a,a2,...,ak这些因子,共计k+1k+1k+1个)

只要k≥1k\ge 1k1,一定有2k≥k+12k\ge k+12kk+1,因此拆分是最优解

例如8,只有一个质因子2,不拆分的话就只有1 2 4 8这4个因子,拆了就是3个2,共计6个因子,因此拆分比不拆分的因子个数要多。

  • 情况2:xxx有多个质因子,即x=a1k1×a2k2×...amkmx=a_1^{k_1}\times a_2^{k_2}\times ...a_m^{k_m}x=a1k1×a2k2×...amkm

拆分:根据情况1的计算公式,权值总和为∑i=1m(ki+1)\sum_{i=1}^{m}(k_i+1)i=1m(ki+1)

不拆分:根据情况1的计算公式,权值总和为∏i=1m(yi+1)\prod _{i=1}^{m}(y_i+1)i=1m(yi+1)

我们可以明显得知,当a,b≥2时a,b\ge 2时a,b2a×b≥a+ba\times b\ge a+ba×ba+b

因此选择不拆分是最优解

例如30,有质因子2,3,5,如果不拆分的话有1,2,3,5,6,10,15,30这8个因子,如果拆分的话,就是1个2,1个3,1个5这6个因子,因此不拆分要比拆分的因子个数要多。

C++

#include<bits/stdc++.h>
using namespace std;
const int N=1E5+10,mod=1e9+7;
int T,n;
long long solve(int x){
    vector<pair<int,int>>v;  //记录质因子和其出现的次数
    for(int i=2;i*i<=x;i++){
        if(x%i==0){
            int cnt=0;
            while(x%i==0){
                cnt++;
                x/=i;
            }
            v.push_back({i,cnt});
        }
    }
    if(x>1){
        v.push_back({x,1});
    }
    if(v.size()==1)return v[0].second*2;  //只有一个质因子
    long long res=1;
    for(auto &t:v){
        res*=(t.second+1);
    }
    return res;
}
int main(){
    cin>>T;
    while(T--){
        cin>>n;
        cout<<solve(n)<<endl;
    }
    return 0;
}

Java

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int T = scanner.nextInt();
        while(T-- > 0) {
            int n = scanner.nextInt();
            System.out.println(solve(n));
        }
    }

    private static long solve(int x) {
        List<Pair<Integer, Integer>> v = new ArrayList<>();  // 记录质因子和其出现的次数
        for(int i = 2; i * i <= x; i++) {
            if(x % i == 0) {
                int cnt = 0;
                while(x % i == 0) {
                    cnt++;
                    x /= i;
                }
                v.add(new Pair<>(i, cnt));
            }
        }
        if(x > 1) {
            v.add(new Pair<>(x, 1));
        }
        if(v.size() == 1) return v.get(0).second * 2;  // 只有一个质因子
        long res = 1;
        for(Pair<Integer, Integer> t : v) {
            res *= (t.second + 1);
        }
        return res;
    }

    static class Pair<T, U> {
        T first;
        U second;

        Pair(T first, U second) {
            this.first = first;
            this.second = second;
        }
    }
}

Python

T = int(input())
def solve(x):
    v = []  # 记录质因子和其出现的次数
    i = 2
    while i * i <= x:
        if x % i:
            i += 1
        else:
            cnt = 0
            while x % i == 0:
                cnt += 1
                x //= i
            v.append((i, cnt))
    if x > 1:
        v.append((x, 1))
    if len(v) == 1: return v[0][1] * 2  # 只有一个质因子
    res = 1
    for t in v:
        res *= (t[1] + 1)
    return res

for _ in range(T):
    n = int(input())
    print(solve(n))
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值