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

第一题:平方数

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

题目描述

ak机拿到一个整数 x x x,并希望通过如下两个操作将 x x x变为完全平方数。

  1. x x x是素数,则将其减1
  2. 否则,将其除以自己最小的素因子。

ak机需要操作多少次?

输入描述

一个正整数 x ( 1 ≤ x ≤ 1 0 9 ) x(1\le x\le 10^9) x(1x109)

输出描述

一个整数,表示操作次数。

样例

输入

5

输出

1

输入

20

输出

3

思路:筛质数+优化技巧

本题我们需要快速判断一个数字 x x x是否是以下三种数字

  • 质数:这里我们可以使用枚举 x x x的因子或者埃氏筛/线性筛的方式判断,复杂度分别为 O ( n ) O(\sqrt{n}) O(n ) O ( 1 ) O(1) O(1),其中埃氏筛的预处理复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)

但是这里有个问题,埃氏筛由于受限于时空间复杂度,一般最多只能判断 1 0 6 10^6 106以内的质数,因此本题需要采用一种优化技巧

对于 x ≤ 1 0 6 x\le 10^6 x106,我们可以使用埃氏筛在 O ( n l o g n ) O(nlogn) O(nlogn)的时间预处理,然后 O ( 1 ) O(1) O(1)的时间判断

对于 x > 1 0 6 x>10^6 x>106,我们直接使用枚举因子的方式判断,复杂度为 O ( n ) O(\sqrt{n}) O(n ),具体如下:

bool check_prime(int x){   //当x>1e5的时候,判断x是否为质数,复杂度为sqrt(n)
    for(int i=2;i*i<=x;i++){
        if(x%i==0)return false;
    }
    return true;
}
  • 完全平方数:直接利用库函数sqrt判断即可,内部库函数实际实现是利用二分或者牛顿迭代法,因此复杂度可以看成 O ( l o g n ) O(logn) O(logn)
  • 非质数的最小质因子:直接利用埃氏筛筛选后的素数表进行枚举即可

复杂度分析

对于操作一而言,复杂度最大为 O ( n ) O(\sqrt{n}) O(n ),对于操作二而言, 1 0 5 10^5 105范围内的质数大概应该是几千左右,而且每经过一次操作2, x x x的值至少要除以2,因此最多执行操作2:30次( 2 30 > 1 0 9 2^{30}>10^9 230>109),因此总的时间复杂度一定是不会超时的。

C++

#include<bits/stdc++.h>
using namespace std;
const int N= 1e5+10;
int n;
set<int>primes; 
bool st[N];
void get_primes(int n)
{
    for (int i = 2; i <= n; i ++ )
    {
        if (st[i]) continue;
        primes.insert(i);
        for (int j = i + i; j <= n; j += i)
            st[j] = true;
    }
}
bool check_1(int x){  //判断x是否为完全平方数,复杂度为logn
    int t=sqrt(x);
    return t*t==x;
}
bool check_prime(int x){   //当x>1e5的时候,判断x是否为质数,复杂度为sqrt(n)
    for(int i=2;i*i<=x;i++){
        if(x%i==0)return false;
    }
    return true;
}
int main()
{
    cin>>n;
    get_primes(N);
    int cnt=0;
    while(!check_1(n)){
        if((n<=1e5&&primes.count(n)||check_prime(n))){   //当前n是质数
            n--;
            cnt++;
        }
        else{
            for(auto &t:primes){
                if(n%t==0){
                    n/=t;
                    cnt++;
                    break;
                }
            }
        }
    }
    cout<<cnt<<endl;
    return 0;
}

Java

import java.util.*;

public class Main {
    static final int N = (int)1e5 + 10;
    static int n;
    static Set<Integer> primes = new HashSet<>();
    static boolean[] st = new boolean[N];

    // 获取小于等于n的所有质数
    static void getPrimes(int n) {
        for (int i = 2; i <= n; i++) {
            if (st[i]) continue;
            primes.add(i);
            for (int j = i + i; j <= n; j += i)
                st[j] = true;
        }
    }

    // 判断x是否为完全平方数,复杂度为logn
    static boolean check1(int x) {
        int t = (int)Math.sqrt(x);
        return t * t == x;
    }

    // 当x>1e5的时候,判断x是否为质数,复杂度为sqrt(n)
    static boolean checkPrime(int x) {
        for (int i = 2; i * i <= x; i++) {
            if (x % i == 0) return false;
        }
        return true;
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        n = scanner.nextInt();
        getPrimes(N-1);
        int cnt = 0;
        while (!check1(n)) {
            if ((n <= 1e5 && primes.contains(n)) || checkPrime(n)) {   // 当前n是质数
                n--;
                cnt++;
            } else {
                for (int t : primes) {
                    if (n % t == 0) {
                        n /= t;
                        cnt++;
                        break;
                    }
                }
            }
        }
        System.out.println(cnt);
    }
}

Python

import math

N = int(1e5 + 10)
n = 0
primes = set()
st = [False] * N

# 获取小于等于n的所有质数
def get_primes(n):
    for i in range(2, n):
        if st[i]: continue
        primes.add(i)
        j = i + i
        while j < n:
            st[j] = True
            j += i

# 判断x是否为完全平方数,复杂度为logn
def check_1(x):
    t = int(math.sqrt(x))
    return t * t == x

# 当x>1e5的时候,判断x是否为质数,复杂度为sqrt(n)
def check_prime(x):
    i = 2
    while i * i <= x:
        if x % i == 0: return False
        i += 1
    return True

n = int(input())
get_primes(N)
cnt = 0
while not check_1(n):
    if (n <= 1e5 and n in primes) or check_prime(n):   # 当前n是质数
        n -= 1
        cnt += 1
    else:
        for t in primes:
            if n % t == 0:
                n //= t
                cnt += 1
                break
print(cnt)

第二题:跳格子

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

题目描述

ak机小时候很喜欢一个叫跳格子的游戏,地上总共有 n n n几个格子,ak机跳到第 i i i个格子可以获得 a i a_i ai的分数。
ak机从第1个格子开始,ak机每次可以跳跃一个悲波那契数的长度,她想知道她恰好跳到第 n n n个格子最多可以获得多少分。
所谓斐波那契数,指斐波那契数列:1,1,2,3,5,8.….中的某一项。悲波那契数列满足,第三项开始,每一项等于前两项之和。

输入描述

第一行输入一个整数 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 0 9 ≤ a i ≤ 1 0 9 ) a_i(-10^9\le a_i\le 10^9) ai(109ai109)

输出描述

输出一个整数表示答案:

样例1

输入

3
1 2 3

输出

6

说明

第1步,跳跃1格,跳到第2个格子
第2步,跳跃1格,跳到第3个格子。
获得的分数为6。

样例2

输入

3
1 -2 3

输出

4

思路:线性DP

这道题,其实就是跳跳棋的加强版,上面一道题每一次跳的格子,要么跳一格,要么跳两格,并且不能连续两次跳一格,这道题就是每次跳的格子可以自己选,但必须要是斐波拉契数列中的一项,那么根据上面一道题的状态转移方程,我们可以知道,如果有 m m m个斐波拉契数,数组长度 n n n,对应的时间复杂度为 O ( n × m ) O(n\times m) O(n×m)
这样看上去似乎时间复杂度很大,有超时的风险,但是,我们要明白一个限制条件:跳跃的长度不可能超过数组的长度 n n n,其中 n ≤ 2 × 1 0 5 n\le 2\times 10^5 n2×105,我们考虑, ≤ 2 × 1 0 5 \le 2\times 10^5 2×105的斐波拉契数有几个,我们可以根据斐波拉契数列(二)的代码可以计算出, ≤ 2 × 1 0 5 \le 2\times 10^5 2×105的斐波拉契数有28个,也就是说, m ≤ 28 m\le 28 m28
因此对应的时间复杂度 O ( n × m ) ≤ O ( 2 × 1 0 5 × 28 ) = O ( 5.6 × 1 0 6 ) O(n\times m)\le O(2\times 10^5\times 28)=O(5.6\times 10^6) O(n×m)O(2×105×28)=O(5.6×106),这个是完全可以接受的,因此我们就结合斐波拉契数列(二)跳跳棋的代码,来求解本题。
我们按照动态规划的三要素来分析这道题:状态方程定义、状态初始化、状态转移方程
状态方程定义
定义 f [ i ] f[i] f[i]为跳到前 i i i个格子的最大得分
状态初始化
我们将起始位置视为1号位置,则有 f [ 1 ] = w [ 1 ] f[1]=w[1] f[1]=w[1]
状态转移方程
我们预先处理出最后一项不超过 n n n的斐波拉西数列 b b b,设 b b b数组的长度为 m m m
对于当前的位置 i i i,它上一步可以由 i − b [ j ] i-b[j] ib[j]跳过来,其中 0 ≤ j ≤ m − 1 0\le j\le m-1 0jm1
因此有 f [ i ] = m a x ( f [ i ] , f [ i − b [ j ] ] + w [ i ] ) f[i]=max(f[i],f[i-b[j]]+w[i]) f[i]=max(f[i],f[ib[j]]+w[i])
最终输出 f [ n ] f[n] f[n]即可

C++

#include<bits/stdc++.h>
using namespace std;
const int N=2E5+10;
int n,a[N];
long long f[N];
int main(){
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i];
    vector<int>b={1,1};   //斐波那契数列
    for(int i=2;b.back()<=n;i++){
        b.push_back(b[i-1]+b[i-2]);
    }
    int m=b.size();
    memset(f,-0x3f,sizeof f);
    f[1]=a[1];
    for(int i=2;i<=n;i++){
        for(int j=1;j<m;j++){
            int k=i-b[j];
            if(k<0)break;
            f[i]=max(f[i],f[k]+a[i]);
        }
    }
    cout<<f[n]<<endl;
    return 0;
}

Java

import java.util.*;

public class Main {
    static final int N = (int)2E5 + 10;
    static int n;
    static int[] a = new int[N];
    static long[] f = new long[N];

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        n = scanner.nextInt();
        for (int i = 1; i <= n; i++) a[i] = scanner.nextInt();
        List<Integer> b = new ArrayList<>(Arrays.asList(1, 1));   // 斐波那契数列
        for (int i = 2; b.get(b.size() - 1) <= n; i++) {
            b.add(b.get(i - 1) + b.get(i - 2));
        }
        int m = b.size();
        Arrays.fill(f, Long.MIN_VALUE);
        f[1] = a[1];
        for (int i = 2; i <= n; i++) {
            for (int j = 1; j < m; j++) {
                int k = i - b.get(j);
                if (k < 0) break;
                f[i] = Math.max(f[i], f[k] + a[i]);
            }
        }
        System.out.println(f[n]);
    }
}

Python

n = int(input())
a = [0] + list(map(int, input().split()))
b = [1, 1]   # 斐波那契数列
while b[-1] <= n:
    b.append(b[-1] + b[-2])
m = len(b)
f = [float('-inf')] * (n + 1)
f[1] = a[1]
for i in range(2, n + 1):
    for j in range(1, m):
        k = i - b[j]
        if k < 0: break
        f[i] = max(f[i], f[k] + a[i])
print(f[n])

第三题:2024快过去!

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

题目描述

ak机非常喜欢一部 2023 年上线的作品,但是这部作品到 2025年才能有第二季,ak机觉得这个 2024 年不需要了,想直接结束2024年,因此她现在,常喜欢3和5但不喜欢4。
现在ak机有一个数组,她想知道这个数组中有多少个子序列的和是3和5的倍数,但不是4的倍数。

由于这个答案可能很大,因此你需要输一答案对 1 0 9 + 7 10^9+7 109+7取模后的结果。

输入描述

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

第二行输入 n n n个整数 a ( − 1 0 9 ≤ a i ≤ 1 0 9 ) a(-10^9\le a_i\le 10^9) a(109ai109)表示数组

输出描述

输出一个整数表示答案

样例

输入

3
13 30 17

输出

2

说明

有两个子序列满足要求:[13,17],[30]。

思路:动态规划+容斥定理

首先我们思考,什么样的数字是3的倍数,显然是对3取余结果为0的数字,即如果数字 x x x是3的倍数,则有 x % 3 = 0 x\%3=0 x%3=0

什么样的数字是5的倍数,显然是对5取余结果为0的数字,即如果数字 x x x是5的倍数,则有 x % 5 = 0 x\%5=0 x%5=0

那么问题来了,什么样的数字,既是3的倍数,又是5的倍数,显然,这个数字一定是15的倍数(因为3和5的最大公约数是1)

因此,我们先解决第一个问题,求出是3和5的倍数的个数 c n t 1 cnt1 cnt1

我们按照动态规划的三要素来分析这道题:状态方程定义、状态初始化、状态转移方程

状态方程定义

首先,所有的数字,对15取模的结果,一定是属于区间 [ 0 , 14 ] [0,14] [0,14]范围内的

因此,我们定义 f [ i ] [ j ] f[i][j] f[i][j]来表示选择前 i i i个数字,且数字对15取模的结果为 j j j的方案数

状态初始化

我们定义数组下标从1开始,一开始什么数字都不选,和为0,因此有 f [ 0 ] [ 0 ] = 1 f[0][0]=1 f[0][0]=1(表示空数组的情况)

状态转移方程

当我们枚举到数组的第 i i i个位置,有以下两种情况

  • 不选择第 i i i个数字,则对于任意的 0 ≤ j ≤ 14 0\le j\le 14 0j14,有 f [ i ] [ j ] + = f [ i − 1 ] [ j ] f[i][j]+=f[i-1][j] f[i][j]+=f[i1][j]
  • 选择第 i i i个数字,我们设第 i i i个数字对15取模的结果为 x x x,则对于任意的 0 ≤ j ≤ 1 0\le j\le 1 0j1,有 f [ i ] [ ( j + x ) % 15 ] + = f [ i − 1 ] [ j ] f[i][(j+x)\%15]+=f[i-1][j] f[i][(j+x)%15]+=f[i1][j]

最终有 c n t 1 = f [ n ] [ 0 ] cnt1=f[n][0] cnt1=f[n][0]

但本题还需要去把4的倍数的方案数给去除。

我们就需要考虑,什么数字 x x x,是3、4、5的倍数,显然,这个数字 x x x一定是60的倍数。

因此,根据容斥定理,我们还需要去除子序列中和是60的倍数的方案数。

我们按照上述的定义规则,定义 g [ i ] [ j ] g[i][j] g[i][j]表示选择前 i i i个数字,且数字对60取模的结果为 j j j的方案数

最终答案即为 f [ n ] [ 0 ] − g [ n ] [ 0 ] f[n][0]-g[n][0] f[n][0]g[n][0]

C++

#include<bits/stdc++.h>
using namespace std;
const int N=1E5+10,mod=1e9+7;
int n,a[N];
long long f[N][15],g[N][60];  //f[i][j]/g[i][j]表示选择前i个数字,其中和%15/60的结果为j的方案数
int main(){
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i];
    f[0][0]=g[0][0]=1;
    for(int i=1;i<=n;i++){
        for(int j=0;j<15;j++){
            f[i][j]=(f[i][j]+f[i-1][j])%mod;  //不选择第i个数字
            f[i][(j+a[i])%15]=(f[i][(j+a[i])%15]+f[i-1][j])%mod;  //选择第i个数字
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=0;j<60;j++){
            g[i][j]=(g[i][j]+g[i-1][j])%mod;  //不选择第i个数字
            g[i][(j+a[i])%60]=(g[i][(j+a[i])%60]+g[i-1][j])%mod;  //选择第i个数字
        }
    }
    long long res=(f[n][0]-g[n][0]+mod)%mod;
    cout<<res<<endl;
    return 0;
}

Java

import java.util.Scanner;

public class Main {
    static final int N = 100010;
    static final int mod = (int)1e9+7;
    static int n;
    static int[] a = new int[N];
    static long[][] f = new long[N][15];
    static long[][] g = new long[N][60];

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        n = in.nextInt();
        for(int i=1; i<=n; i++) a[i] = in.nextInt();
        f[0][0] = g[0][0] = 1;
        for(int i=1; i<=n; i++){
            for(int j=0; j<15; j++){
                f[i][j] = f[i-1][j] % mod;  //不选择第i个数字
            }
            for(int j=0; j<15; j++){
                f[i][(j+a[i])%15] = (f[i][(j+a[i])%15] + f[i-1][j]) % mod;  //选择第i个数字
            }
        }
        for(int i=1; i<=n; i++){
            for(int j=0; j<60; j++){
                g[i][j] = g[i-1][j] % mod;  //不选择第i个数字
            }
            for(int j=0; j<60; j++){
                g[i][(j+a[i])%60] = (g[i][(j+a[i])%60] + g[i-1][j]) % mod;  //选择第i个数字
            }
        }
        long res = (f[n][0] - g[n][0] + mod) % mod;
        System.out.println(res);
    }
}

Python

n = int(input())
a = list(map(int, input().split()))
N = 100010
mod = 10**9+7
f = [[0]*15 for _ in range(N)]
g = [[0]*60 for _ in range(N)]
f[0][0] = g[0][0] = 1
for i in range(1, n+1):
    for j in range(15):
        f[i][j] = f[i-1][j] % mod  # 不选择第i个数字
    for j in range(15):
        f[i][(j+a[i-1])%15] = (f[i][(j+a[i-1])%15] + f[i-1][j]) % mod  # 选择第i个数字
for i in range(1, n+1):
    for j in range(60):
        g[i][j] = g[i-1][j] % mod  # 不选择第i个数字
    for j in range(60):
        g[i][(j+a[i-1])%60] = (g[i][(j+a[i-1])%60] + g[i-1][j]) % mod  # 选择第i个数字
res = (f[n][0] - g[n][0] + mod) % mod
print(res)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值