23年秋招-米哈游-笔试真题卷(2)

第一题:棋盘

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

题目描述

薯条哥有一个 n × m n\times m n×m 的棋盘,一次移动可以选择上下左右四个方向移动一次,不同于普通棋盘,这个棋盘是循环的。

( x , m ) (x, m) (x,m) ( x , 1 ) (x, 1) (x,1) 两个点可以一步到达,其中 1 ≤ x ≤ n 1\leq x\leq n 1xn 。同样的, ( n , y ) (n, y) (n,y) ( 1 , y ) (1, y) (1,y) 两个点也可以一步到达,其中 1 ≤ y ≤ m 1\leq y\leq m 1ym

现在薯条哥需要从 A A A 点先走到 B B B 点,再从 B B B 点走到 C C C 点,问最小移动次数是多少。

输入描述

第一行两个整数, n n n m m m
接下来三行,第一行是点 A A A 的坐标 ( x A , y A ) (x_A,y_A) (xA,yA),第二行是点 B B B 的坐标 ( x B , y B ) (x_B,y_B) (xB,yB) ,第三行是点 C C C 的坐标 ( x C , y C ) (x_C,y_C) (xC,yC)

1 ≤ n , m ≤ 1 0 9 , 1 ≤ x A , x B , x C ≤ n , 1 ≤ y A , y B , y C ≤ m 1\leq n,m\leq 10^9, 1\leq x_A,x_B,x_C\leq n, 1\leq y_A,y_B,y_C\leq m 1n,m109,1xA,xB,xCn,1yA,yB,yCm

输出描述

输出从 A A A B B B ,再从 B B B C C C 的最小移动次数。

样例

输入

4 4
1 2
1 3
1 4

输出

2

题解:模拟

考虑任意点 ( a , b ) (a,b) (a,b) ( c , d ) (c,d) (c,d)最小移动距离

首先,我们可以考虑从 a a a c c c的移动距离

  • 方式1:直接到达: d i s t = a b s ( a − c ) dist=abs(a-c) dist=abs(ac)
  • 方式2:先走到 n n n,再由 n n n走到1,再由1走到 c c c d i s t = n − a b s ( a − c ) dist=n-abs(a-c) dist=nabs(ac)

两种方式的移动取最小值, d i s t = m i n ( a b s ( a − c ) , n − a b s ( a − c ) ) dist=min(abs(a-c),n-abs(a-c)) dist=min(abs(ac),nabs(ac))

因此 ( a , b ) (a,b) (a,b) ( c , d ) (c,d) (c,d)最小移动距离 d i s t = m i n ( a b s ( a − c ) , n − a b s ( a − c ) ) + m i n ( a b s ( b − d ) , m − a b s ( b − d ) ) dist=min(abs(a-c),n-abs(a-c))+min(abs(b-d),m-abs(b-d)) dist=min(abs(ac),nabs(ac))+min(abs(bd),mabs(bd))

按照上述公式模拟即可

C++

#include<bits/stdc++.h>
using namespace std;
int main(){
    int n,m;
    cin>>n>>m;
    int xA,yA,xB,yB,xC,yC;
    cin>>xA>>yA;
    cin>>xB>>yB;
    cin>>xC>>yC;
    int res=0;
    res+=min(abs(xA-xB),n-abs(xA-xB));
    res+=min(abs(yA-yB),m-abs(yA-yB));   //计算A->B最短距离
    res+=min(abs(xB-xC),n-abs(xB-xC));
    res+=min(abs(yB-yC),m-abs(yB-yC));  // 计算B->C最短距离
    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 m = scanner.nextInt();
        int xA = scanner.nextInt();
        int yA = scanner.nextInt();
        int xB = scanner.nextInt();
        int yB = scanner.nextInt();
        int xC = scanner.nextInt();
        int yC = scanner.nextInt();
        int res = 0;
        res += Math.min(Math.abs(xA - xB), n - Math.abs(xA - xB));
        res += Math.min(Math.abs(yA - yB), m - Math.abs(yA - yB)); // 计算A到B的最短距离
        res += Math.min(Math.abs(xB - xC), n - Math.abs(xB - xC));
        res += Math.min(Math.abs(yB - yC), m - Math.abs(yB - yC)); // 计算B到C的最短距离
        System.out.println(res);
    }
}

Python

n, m = map(int, input().split())
xA, yA = map(int, input().split())
xB, yB = map(int, input().split())
xC, yC = map(int, input().split())
res = 0
res += min(abs(xA - xB), n - abs(xA - xB))
res += min(abs(yA - yB), m - abs(yA - yB)) # 计算A到B的最短距离
res += min(abs(xB - xC), n - abs(xB - xC))
res += min(abs(yB - yC), m - abs(yB - yC)) # 计算B到C的最短距离
print(res)

第二题:有根树的节点个数

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

题目描述

薯条哥有一个 n n n 个节点的树,树根编号为 1 1 1

薯条哥可以在叶子节点上添加一个新的儿子节点,添加后,添加的节点变成了新的叶子节点。

若干次操作后,薯条哥想问你距离树根不超过 k k k 的节点最多可以有多少个。

输入描述

第一行,一个正整数 n n n 表示树中节点个数, k k k 表示不超过树根的距离,

接下来 n − 1 n-1 n1 行,每行输入两个整数 u u u v v v ,表示节点 u u u v v v 之间有一条边。

1 ≤ n ≤ 1 0 5 , 1 ≤ k ≤ 1 0 9 , 1 ≤ u , v ≤ n 1 \leq n \leq 10^5, 1\leq k\leq 10^9, 1\leq u,v\leq n 1n105,1k109,1u,vn

输出描述

一个整数,表示若干次操作后距离树根不超过 k k k 的节点最大数量。

样例

输入

4 2
1 2
1 3
1 4

输出

7

说明

样例解释

本身有4个节点到根节点的距离不超过k(1,2,3,4)
叶子节点是(2,3,4) 还可以再添加一个节点
因此总共的数量为4+3=7

题解:树形DFS

本题需要用到贡献法计数的思想:就是考虑每一个节点对答案的贡献

如果不进行任意的操作,那么对应的答案就是树中所有与根节点距离 ≤ k \le k k的节点总数 c n t cnt cnt

我们现在需要去考虑:可以通过对叶子节点新增节点对答案的贡献。

首先,我们需要找到叶子结点,那什么样的节点是叶子结点?

显然,出度为0的节点就是叶子节点(入度为0的节点是根节点),但是本题给的是无向边,没办法去统计出度,我们换一个思路,如果一个节点是叶子结点,那么它有且仅有1条边与之相连(就是它的父节点和它构成的边)

知道了如何判断叶子节点,那么我们就需要考虑,对于每一个叶子节点,如何计算其对答案的贡献,也就是需要考虑其可以添加几个节点。这其实就跟叶子结点距离根节点的距离有关。

我们定义 d i s t [ i ] dist[i] dist[i]表示节点 i i i到根节点的距离,初始化 d i s t [ 1 ] = 0 dist[1]=0 dist[1]=0

那么,在我们进行DFS遍历的时候,如果 u u u v v v的子节点,一定有 d i s t [ u ] = d i s t [ v ] + 1 dist[u]=dist[v]+1 dist[u]=dist[v]+1

因此,我们按照上一道题:子树节点个数的dfs代码,可以预处理出 d i s t dist dist数组的值。

然后,对于每一个叶子结点,我们可以把答案累加上 a n s = a n s + m a x ( 0 , k − d i s t [ i ] ) ans=ans+max(0,k-dist[i]) ans=ans+max(0,kdist[i])

注意:本题数据范围较大 a n s ans ans变量需要取long long

C++

#include <bits/stdc++.h>
using namespace std;
const int N=1E5+10;
vector<int>g[N];  //领接表的vector写法 仅适用于点权建图
int dist[N];    //dist[i]记录节点i到根节点的距离
long long ans;
int n,k;
void dfs(int u,int fa) //如果是有向图 就不需要fa这个变量
{
    if(dist[u]<=k){
        ans++;
    }
    if(g[u].size()==1&&u!=1){  //u节点的度为1,且u节点不为根节点,说明该节点为叶子节点
        ans+=max(0,k-dist[u]);  //方案数累加
    }
    for(int &v:g[u]){
        if(v==fa)continue;
        dist[v]=dist[u]+1;
        dfs(v,u);
    }
}

int main(){
    cin>>n>>k;
    for(int i=1;i<n;i++){
        int a,b;
        cin>>a>>b;
        g[a].push_back(b);  //a->b建立一条边
        g[b].push_back(a);   //b->a建立一条边
    }
    dist[1]=0;  //初始化根节点的值
    dfs(1,0);  //从根节点开始,自顶向下搜索
    cout<<ans<<endl;
    return 0;
}

Java

import java.util.*;

public class Main {
    static final int N = (int)1e5+10;
    static List<Integer>[] g = new ArrayList[N];  // 邻接表的ArrayList写法,仅适用于点权建图
    static int[] dist = new int[N];  // dist[i]记录节点i到根节点的距离
    static long ans;
    static int n, k;

    static void dfs(int u, int fa) {  // 如果是有向图,就不需要fa这个变量
        if(dist[u] <= k) {
            ans++;
        }
        if(g[u].size() == 1 && u != 1) {  // u节点的度为1,且u节点不为根节点,说明该节点为叶子节点
            ans += Math.max(0, k - dist[u]);  // 方案数累加
        }
        for(int v : g[u]) {
            if(v == fa) continue;
            dist[v] = dist[u] + 1;
            dfs(v, u);
        }
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        k = sc.nextInt();
        for(int i = 1; i <= n; i++) {
            g[i] = new ArrayList<>();
        }
        for(int i = 1; i < n; i++) {
            int a = sc.nextInt();
            int b = sc.nextInt();
            g[a].add(b);  // a->b建立一条边
            g[b].add(a);  // b->a建立一条边
        }
        dist[1] = 0;  // 初始化根节点的值
        dfs(1, 0);  // 从根节点开始,自顶向下搜索
        System.out.println(ans);
    }
}

Python3

import sys
sys.setrecursionlimit(10**6)  # 设置递归深度上限
from collections import defaultdict

N = int(1e5) + 10
g = defaultdict(list)  # 邻接表的字典写法,仅适用于点权建图
dist = [0] * N  # dist[i]记录节点i到根节点的距离
ans = 0
n, k = 0, 0

def dfs(u, fa):  # 如果是有向图,就不需要fa这个变量
    global ans
    if dist[u] <= k:
        ans += 1
    if len(g[u]) == 1 and u != 1:  # u节点的度为1,且u节点不为根节点,说明该节点为叶子节点
        ans += max(0, k - dist[u])  # 方案数累加
    for v in g[u]:
        if v == fa:
            continue
        dist[v] = dist[u] + 1
        dfs(v, u)



n, k = map(int, input().split())
for _ in range(1, n):
    a, b = map(int, input().split())
    g[a].append(b)  # a->b建立一条边
    g[b].append(a)  # b->a建立一条边
dist[1] = 0  # 初始化根节点的值
dfs(1, 0)  # 从根节点开始,自顶向下搜索
print(ans)

第三题:抽卡程序

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

题目描述

薯条哥很喜欢抽卡的感觉,但是他又没有那么多钱给游戏充钱,所以他写了个抽卡程序。

普通抽卡过程有三种抽卡结果,5星常驻卡、5星限定卡和非5星卡。

抽到5星常驻卡和5星限定卡的概率均为 p 2 \frac{p}{2} 2p ,抽到非5星卡的概率为 1 − p 1-p 1p

如果抽到了5星常驻卡,则之后的抽卡结果将发生变化,变为 p p p 的概率抽到5星限定卡, 1 − p 1-p 1p 的概率抽到非5星卡。
如果连续89次都未抽到5星卡,则第90抽必然会抽到一张5星常驻卡或5星限定卡。

现在薯条哥想问你,他写的这个抽卡程序抽到5星限定卡的抽卡次数期望是多少?

输入描述

一个小数 p p p 表示抽卡概率, 0 < p < 1 0 < p < 1 0<p<1

输出描述

一个小数,表示抽到5星限定卡的抽卡次数期望,请你将输出结果保留小数点后7位。

样例

输入

0.001

输出

129.1649522

题解:树形DP

本题有一个非常巧妙的做法, 但考虑到让大家更好地理解期望DP,我还是从一个最基本的DP的思想来跟大家讲解这道题的思路。

首先,显然如果薯条哥非常非,他需要抽满180次,才能保证自己抽到5星限定卡,也就是说,我们如果定义 f [ i ] f[i] f[i]为第 i i i次抽到5星限定卡的概率,那么总的期望就为 E = ∑ i = 1 180 f [ i ] × i E=\sum_{i=1}^{180}f[i]\times i E=i=1180f[i]×i

我们先考虑一个简单的情况,也就是前90抽的情况

我们发现一个很重要的结论:5星常驻卡和5星限定卡的出货概率是独立且相同的,各占50%!

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

状态方程定义

为了便于我们后面计算,我们定义 f [ i ] f[i] f[i]为抽卡次数为 i i i抽到5星卡的概率。

状态初始化

所有状态初始化为0即可。

状态转移方程

我们发现,第 i i i次抽到5星卡的概率,说明前 i − 1 i-1 i1次都没有抽到5星卡,并且第 i i i次抽到了5星卡

因此有 f [ i ] = ( 1 − ∑ j = 1 j = i − 1 f [ j ] ) × p f[i]=(1-\sum_{j=1}^{j=i-1}f[j])\times p f[i]=(1j=1j=i1f[j])×p

然后,对于第 i i i次抽到5星限定卡的期望就等于 f [ i ] × i × 1 2 f[i]\times i\times \frac{1}{2} f[i]×i×21

特殊情况 i = 90 i=90 i=90的时候, p p p为固定值0.5。

然后我们考虑从91~180抽的期望的计算方式

我们可以发现,这个是有非常多的情况,我们拿81抽出货举例,有以下几种情况

  • 情况1:第1抽就出了5星卡,但是歪到了常驻5星,然后又接连抽了90抽,才抽到限定5星
  • 情况2:第2抽就出了5星卡,但是歪到了常驻5星,然后又接连抽了89抽,才抽到限定5星
  • 情况3:第3抽就出了5星卡,但是歪到了常驻5星,然后又接连抽了88抽,才抽到限定5星

因此,我们可以写一个双重循环,来枚举这些情况,注意,歪了常驻5星之后,后面每次抽到限定5星的概率不需要除以2(大保底必定不歪!!)

具体可以参考下面代码和代码中的注释

C++

#include <bits/stdc++.h>
using namespace std;
double f[91];  //f[i]表示抽卡次数为i的时候,出五星卡的概率
double s[91];  //s[i]表示f[i]的前缀和数组
double p; //出五星的概率
int main() {
    double res=0;
    cin>>p;
    for(int i=1;i<90;i++){  //先计算1~89抽的期望,并更新f[i]和s[i]数组
        f[i]=(1-s[i-1])*p; 
        res+=f[i]*i/2.0;  //累加期望(出限定五星的概率仅为50%)
        s[i]=s[i-1]+f[i];  //前缀和数组更新
    }
    f[90]=(1-s[89])*1.0;  //第90抽必出五星
    res+=90*f[90]/2.0;   //第90抽的期望,出限定五星的概率仅为50%
    for(int i=1;i<=90;i++){   //考虑小保底之后出货的期望
        for(int j=1;j<=90;j++){
            double p1=f[i]/2.0,p2=f[j];  //p1为第i抽抽到常驻五星的概率,p2为第i抽之后,抽到第j抽抽到限定五星的概率
            res+=(i+j)*p1*p2;
        }
    }
    cout << fixed;
    cout.precision(7);   //保留小数点后7位
    cout <<res<<endl;
    return 0;
}

Java

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        double[] f = new double[91];  // f[i]表示抽卡次数为i的时候,出五星卡的概率
        double[] s = new double[91];  // s[i]表示f[i]的前缀和数组
        double p;  // 出五星的概率

        Scanner scanner = new Scanner(System.in);
        p = scanner.nextDouble();

        double res = 0;
        for (int i = 1; i < 90; i++) {  // 先计算1~89抽的期望,并更新f[i]和s[i]数组
            f[i] = (1 - s[i - 1]) * p;
            res += f[i] * i / 2.0;  // 累加期望(出限定五星的概率仅为50%)
            s[i] = s[i - 1] + f[i];  // 前缀和数组更新
        }
        f[90] = (1 - s[89]) * 1.0;  // 第90抽必出五星
        res += 90 * f[90] / 2.0;  // 第90抽的期望,出限定五星的概率仅为50%
        for (int i = 1; i <= 90; i++) {  // 考虑小保底之后出货的期望
            for (int j = 1; j <= 90; j++) {
                double p1 = f[i] / 2.0, p2 = f[j];  // p1为第i抽抽到常驻五星的概率,p2为第i抽之后,抽到第j抽抽到限定五星的概率
                res += (i + j) * p1 * p2;
            }
        }
        System.out.printf("%.7f\n", res);  // 保留小数点后7位
    }
}

Python

f = [0.0] * 91  # f[i]表示抽卡次数为i的时候,出五星卡的概率
s = [0.0] * 91  # s[i]表示f[i]的前缀和数组

p = float(input())  # 出五星的概率

res = 0.0
for i in range(1, 90):  # 先计算1~89抽的期望,并更新f[i]和s[i]数组
    f[i] = (1 - s[i - 1]) * p
    res += f[i] * i / 2.0  # 累加期望(出限定五星的概率仅为50%)
    s[i] = s[i - 1] + f[i]  # 前缀和数组更新

f[90] = (1 - s[89]) * 1.0  # 第90抽必出五星
res += 90 * f[90] / 2.0  # 第90抽的期望,出限定五星的概率仅为50%

for i in range(1, 91):  # 考虑小保底之后出货的期望
    for j in range(1, 91):
        p1 = f[i] / 2.0  # p1为第i抽抽到常驻五星的概率
        p2 = f[j]  # p2为第i抽之后,抽到第j抽抽到限定五星的概率
        res += (i + j) * p1 * p2

print("{:.7f}".format(res))  # 保留小数点后7位
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值