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

第一题:连通图

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

题目描述

给定一个只有 n n n个点的图,图上没有边,ak机准备在这张图上连 m m m 条无向边,将这个图变成无自环无重边的简单连通图,ak机想知道是否存在至少一种连边方案。

输入描述

第一行输入一个整数 T ( 1 ≤ T ≤ 1 0 5 ) T(1 \leq T \leq 10^5) T(1T105) 表示询问数量。

第二行输入两个整数 n , m ( 1 ≤ n , m ≤ 1 0 9 ) n,m(1 \leq n,m \leq 10^9) n,m(1n,m109)

输出描述

对于每个询问,若存在至少一种连边方案,则输出 “YES” 。否则输出 “NO” 。

样例

输入

2
3 3
3 1

输出

YES
NO

思路:模拟 简单图论

对于一个无自环重边的无向图来说,他至少需要 n − 1 n-1 n1条边才可以连通,例如1->2->...->n,这就可以构成一种需要边数最少的连通图

n n n个点,两两可以连边,最多可以连接 C ( n , 2 ) = n × ( n − 1 ) 2 C(n,2)=\frac{n\times (n-1)}{2} C(n,2)=2n×(n1)条边,因此最多的边数不能超过 n × ( n − 1 ) 2 \frac{n\times (n-1)}{2} 2n×(n1)

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

C++

#include <bits/stdc++.h>  
using namespace std;
int n,m,T;
int main() {
    cin>>T;
    while(T--){
        cin>>n>>m;
        if(m>=n-1&&m<=1ll*n*(n+1)/2)puts("YES");
        else puts("NO");
    }
}

Java

import java.util.Scanner;

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();
            int m = scanner.nextInt();
            if (m >= n - 1 && m <= 1L * n * (n + 1) / 2) {
                System.out.println("YES");
            } else {
                System.out.println("NO");
            }
        }
    }
}

Python

T = int(input())
for _ in range(T):
    n, m = map(int, input().split())
    if m >= n - 1 and m <= n * (n + 1) // 2:
        print("YES")
    else:
        print("NO")

第二题:流血debuff的最大层数

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

题目描述

ak机面前有 n n n个怪物,站成一排,最初每个怪物有 a i a_i ai的流血状态,ak机可以进行 m m m次攻击,每次攻击选择 k k k个连续的怪物,每个怪物增加 1 层流血状态。ak机想知道 m m m次攻击之后,流血状态最少的怪物最多可以有多少层流血状态。

输入描述

第一行输入三个整数 n , m , k n,m,k n,m,k,表示怪物数量,攻击次数,每次攻击选择的怪物数量。

第二行输入 n n n个整数 a i a_i ai,表示每个怪物初始的流血状态。

1 ≤ k ≤ n ≤ 1 0 5 1 \leq k \leq n \leq 10^5 1kn105

1 ≤ a i , m ≤ 1 0 9 1 \leq a_i, m \leq 10^9 1ai,m109

输出描述

输出一个整数,表示流血状态最少的怪物最多可以有多少层流血状态。

样例

输入

5 3 2
3 1 2 5 4

输出

4

说明

3 次攻击分别选择 [1, 2], [2, 3], [2, 3],最后怪物的流血状态为 [4, 4, 4, 5, 4],流血状态最少的怪物最多有 4 层流血状态。

思路:二分答案+差分check

本题抽象出来其实就是最小值最大化,我们可以考虑使用二分答案求解(遇到这种最大化或者最小化问题,第一反应就要想到二分答案)

二分答案枚举到 m i d mid mid时,我们需要验证是否可以将所有怪物的流血层数都在 m i d mid mid层以上

由于操作是对一个区间操作,即为区间加法,因此我们可以使用差分来维护对区间的操作

维护一个差分数组 d i f f diff diff d i f f [ i ] diff[i] diff[i]表示当前位置 i i i已经增加了 d i f f [ i ] diff[i] diff[i]层流血

那么当我们遍历到位置 i i i时,如果有 d i f f [ i ] + a [ i ] < m i d diff[i]+a[i]<mid diff[i]+a[i]<mid,则我们需要对区间 [ i , i + k − 1 ] [i,i+k-1] [i,i+k1]操作 c n t 1 cnt1 cnt1次,其中 c n t 1 = m i d − d i f f [ i ] − a [ i ] cnt1=mid-diff[i]-a[i] cnt1=middiff[i]a[i]

然后我们使用差分数组模版对区间进行操作,最终判断所有操作次数 c n t cnt cnt是否满足 c n t ≤ m cnt\le m cntm即可

  • 如果所有的操作次数 c n t ≤ m cnt\le m cntm,则说明当前答案满足条件,可以将区间缩小至 [ m i d , r ] [mid,r] [mid,r]
  • 如果所有的操作次数 c n t > m cnt> m cnt>m,则说明当前答案不满足条件,可以将区间缩小至 [ l , m i d − 1 ] [l,mid-1] [l,mid1]

C++

#include <bits/stdc++.h>  
using namespace std;
const int N=1E5+10;
int n,m,k,a[N];
bool check(int x){  //枚举是否所有怪物都可以至少有x层流血状态
    vector<int>diff(n+10,0);  //差分数组
    int cnt=0;
    for(int i=1;i<=n;i++){
        diff[i]+=diff[i-1];
        if(diff[i]+a[i]<x)  //层数不够 需要叠加
        {
            int cnt1=x-a[i]-diff[i];
            cnt+=cnt1;
            diff[i]+=cnt1;   //对区间[i,i+k]+cnt1
            diff[min(n+1,i+k)]-=cnt1;
        }
        if(cnt>m)return false;
    }
    return true;
    
}
int main() {
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++)cin>>a[i];
    int l=1,r=1e9;
    while(l<r){
        int mid=(l+r+1)>>1;
        if(check(mid))l=mid;
        else r=mid-1;
    }
    cout<<l<<endl;
}

Java

import java.util.Scanner;
import java.util.Arrays;

public class Main {
    static int N = (int)1e5 + 10;
    static int n, m, k;
    static int[] a = new int[N];
    static int[] diff = new int[N];

    // 检查所有怪物是否都可以至少有x层流血状态
    static boolean check(int x) {
        Arrays.fill(diff, 0);
        int cnt = 0;
        for (int i = 1; i <= n; i++) {
            diff[i] += diff[i - 1];
            if (diff[i] + a[i] < x) {  // 层数不够,需要叠加
                int cnt1 = x - a[i] - diff[i];
                cnt += cnt1;
                diff[i] += cnt1;  // 对区间[i, i+k]叠加cnt1
                diff[Math.min(n + 1, i + k)] -= cnt1;
            }
            if (cnt > m) return false;
        }
        return true;
    }

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

Python

N = int(1e5) + 10
a = [0] * N
diff = [0] * N

# 检查所有怪物是否都可以至少有x层流血状态
def check(x):
    diff[:] = [0] * N
    cnt = 0
    for i in range(1, n + 1):
        diff[i] += diff[i - 1]
        if diff[i] + a[i] < x:  # 层数不够,需要叠加
            cnt1 = x - a[i] - diff[i]
            cnt += cnt1
            diff[i] += cnt1  # 对区间[i, i+k]叠加cnt1
            diff[min(n + 1, i + k)] -= cnt1
        if cnt > m:
            return False
    return True

n, m, k = map(int, input().split())
for i in range(1, n + 1):
    a[i] = int(input())
l, r = 1, int(1e9)
while l < r:
    mid = (l + r + 1) // 2
    if check(mid):
        l = mid
    else:
        r = mid - 1
print(l)

第三题:子序列乘积

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

题目描述

ak机拿到了一个数组,数组的元素的绝对值不超过 1,她想取一个非空子序列(在原数组中可以不连续),并计算该子序列的乘积。
ak机想知道,子序列乘积为 -1、0、1 的方案数分别有多少种?

输入描述

第一行输入一个正整数 n ( 1 ≤ n ≤ 1 0 5 ) n(1\leq n \leq 10^5) n(1n105),代表数组的大小。

第二行输入 n n n个整数 a i ( − 1 ≤ a i ≤ 1 ) a_i(-1 \leq a_i \leq 1) ai(1ai1),代表数组的元素。

输出描述

三个整数,分别代表子序列乘积为负数、0、正数的方案数。由于答案可能过大,请对 1 0 9 + 7 10^9+7 109+7取模。

样例

输入

4
-1 0 -1 1

输出

4 8 3

说明

长度为 1 的子序列共有 4 个,乘积为 -1 的有 2 个,乘积为 0 的有 1 个,乘积为 1 的有 1 个。
长度为 2 的子序列共有 6 个,乘积为 -1 的有 2 个,乘积为 0 的有 3 个,乘积为 1 的有 1 个。
长度为 3 的子序列共有 4 个,乘积为 -1 的有 0 个,乘积为 0 的有 3 个,乘积为 1 的有 1 个。
长度为 4 的子序列只有 1 个,乘积为 0。

思路:组合计数 乘法逆元

我们首先设数组中-1,0,1的个数分别为 a , b , c a,b,c a,b,c

首先,对于乘积为0的情况,我们只需要选择至少1个0,其余数可以选或者不选都可以,因此对于乘积为0的方案数为 ( 2 b − 1 ) × 2 a + c (2^b-1)\times 2^{a+c} (2b1)×2a+c

对于乘积为1的情况,需要选择偶数个-1,其余的1可以任意选

偶数个-1的情况有 0 , 2 , 4 , . . . 2 k ( 2 k ≤ c ) 0,2,4,...2k(2k\le c) 0,2,4,...2k(2kc)

因此对应的方案数为 ( ∑ i = 0 2 i ≤ c C ( a , 2 i ) × 2 c ) − 1 (\sum_{i=0}^{2i\le c}C(a,2i)\times 2^c)-1 (i=02icC(a,2i)×2c)1

这里减1是因为不能所有的-1和1都不选,这样就成空数组了,因此需要-1。

其中 C ( n , k ) = n ! k ! ( n − k ) ! C(n,k)=\frac{n!}{k!(n-k)!} C(n,k)=k!(nk)!n!

对于乘积为-1的情况,需要选择奇数个-1,其余的1可以任意选

同理可得,对应的方案数为 ∑ i = 0 2 i + 1 ≤ c C ( a , 2 i + 1 ) × 2 c \sum_{i=0}^{2i+1\le c}C(a,2i+1)\times 2^c i=02i+1cC(a,2i+1)×2c

C++

#include<bits/stdc++.h>
using namespace std;
const int mod = 1e9 + 7;
const int N = 1e5+10;
long long fac[N], ifac[N];  //预处理阶乘和阶乘的逆元
string s;
int n,a,b,c,w[N];
long long qmi(long long a, long long b,int mod) {  //快速幂
    long long res = 1;
    while (b > 0) {
        if (b & 1) {
            res = res * a % mod;
        }
        a = a * a % mod;
        b >>= 1;
    }
    return res;
}

long long C(int a, int b) {  //求C(a,b)的组合数
    return fac[a] * ifac[b] % mod * ifac[a - b] % mod;
}
int main() {
    cin>>n;
    for(int i=0;i<n;i++)cin>>w[i];
    for(int i=0;i<n;i++){  //统计数字-1,0,1的个数
        if(w[i]==-1){
            a++;
        }
        else if(w[i]==0){
            b++;
        }
        else{
            c++;
        }
    }
    fac[0] = ifac[0] = 1;
    for (int i = 1; i <=n; i++) {
        fac[i] = fac[i - 1] * i % mod;
        ifac[i]=ifac[i-1]*qmi(i,mod-2,mod)%mod;
    }
    long long res1=0,res2=0,res3=0;  //分别计算乘积为负数、0、正数的方案数
    for(int i=1;i<=a;i+=2){  //计算乘积为负数的个数
        res1=(res1+C(a,i))%mod;
    }
    res1=(res1*qmi(2,c,mod))%mod;
    res2=(qmi(2,b,mod)-1+mod)%mod;
    res2=(res2*qmi(2,a+c,mod))%mod;
    for(int i=0;i<=a;i+=2){  //计算乘积为正数的个数
        res3=(res3+C(a,i))%mod;
    }
    res3=(res3*qmi(2,c,mod))%mod;
    res3=(res3-1+mod)%mod;
    cout<<res1<<" "<<res2<<" "<<res3<<endl;
    return 0;
}

Java

import java.util.Scanner;

public class Main {
    static final int mod = (int)1e9 + 7;
    static final int N = (int)1e5+10;
    static long[] fac = new long[N], ifac = new long[N];  //预处理阶乘和阶乘的逆元
    static int n,a,b,c;
    static int[] w = new int[N];

    static long qmi(long a, long b, int mod) {  //快速幂
        long res = 1;
        while (b > 0) {
            if ((b & 1) == 1) {
                res = res * a % mod;
            }
            a = a * a % mod;
            b >>= 1;
        }
        return res;
    }

    static long C(int a, int b) {  //求C(a,b)的组合数
        return fac[a] * ifac[b] % mod * ifac[a - b] % mod;
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        n = scanner.nextInt();
        for(int i=0;i<n;i++) w[i] = scanner.nextInt();
        for(int i=0;i<n;i++){  //统计数字-1,0,1的个数
            if(w[i]==-1){
                a++;
            }
            else if(w[i]==0){
                b++;
            }
            else{
                c++;
            }
        }
        fac[0] = ifac[0] = 1;
        for (int i = 1; i <=n; i++) {
            fac[i] = fac[i - 1] * i % mod;
            ifac[i]=ifac[i-1]*qmi(i,mod-2,mod)%mod;
        }
        long res1=0,res2=0,res3=0;  //分别计算乘积为负数、0、正数的方案数
        for(int i=1;i<=a;i+=2){  //计算乘积为负数的个数
            res1=(res1+C(a,i))%mod;
        }
        res1=(res1*qmi(2,c,mod))%mod;
        res2=(qmi(2,b,mod)-1+mod)%mod;
        res2=(res2*qmi(2,a+c,mod))%mod;
        for(int i=0;i<=a;i+=2){  //计算乘积为正数的个数
            res3=(res3+C(a,i))%mod;
        }
        res3=(res3*qmi(2,c,mod))%mod;
        res3=(res3-1+mod)%mod;
        System.out.println(res1+" "+res2+" "+res3);
    }
}

Python

mod = 10**9 + 7
N = 10**5+10
fac = [0]*N
ifac = [0]*N  #预处理阶乘和阶乘的逆元
n,a,b,c = 0,0,0,0
w = [0]*N

def qmi(a, b):  #快速幂
    res = 1
    while b > 0:
        if b & 1:
            res = res * a % mod
        a = a * a % mod
        b >>= 1
    return res

def C(a, b):  #求C(a,b)的组合数
    return fac[a] * ifac[b] % mod * ifac[a - b] % mod

n = int(input())
w = list(map(int, input().split()))
for i in range(n):  #统计数字-1,0,1的个数
    if w[i]==-1:
        a += 1
    elif w[i]==0:
        b += 1
    else:
        c += 1
fac[0] = ifac[0] = 1
for i in range(1, n+1):
    fac[i] = fac[i - 1] * i % mod
    ifac[i]=ifac[i-1]*qmi(i,mod-2)%mod
res1,res2,res3 = 0,0,0  #分别计算乘积为负数、0、正数的方案数
for i in range(1,a+1,2):  #计算乘积为负数的个数
    res1=(res1+C(a,i))%mod
res1=(res1*qmi(2,c))%mod
res2=(qmi(2,b)-1+mod)%mod
res2=(res2*qmi(2,a+c))%mod
for i in range(0,a+1,2):  #计算乘积为正数的个数
    res3=(res3+C(a,i))%mod
res3=(res3*qmi(2,c))%mod
res3=(res3-1+mod)%mod
print(res1,res2,res3)
  • 14
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值