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

第一题:棋盘

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

题目描述

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

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

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

输入描述

第一行两个整数,nnnmmm
接下来三行,第一行是点 AAA 的坐标 (xA,yA)(x_A,y_A)(xA,yA),第二行是点 BBB 的坐标 (xB,yB)(x_B,y_B)(xB,yB) ,第三行是点 CCC 的坐标 (xC,yC)(x_C,y_C)(xC,yC)

1≤n,m≤109,1≤xA,xB,xC≤n,1≤yA,yB,yC≤m1\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 m1n,m109,1xA,xB,xCn,1yA,yB,yCm

输出描述

输出从 AAABBB ,再从 BBBCCC 的最小移动次数。

样例

输入

4 4
1 2
1 3
1 4

输出

2

题解:模拟

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

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

  • 方式1:直接到达:dist=abs(a−c)dist=abs(a-c)dist=abs(ac)
  • 方式2:先走到nnn,再由nnn走到1,再由1走到cccdist=n−abs(a−c)dist=n-abs(a-c)dist=nabs(ac)

两种方式的移动取最小值,dist=min(abs(a−c),n−abs(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)最小移动距离dist=min(abs(a−c),n−abs(a−c))+min(abs(b−d),m−abs(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

题目描述

薯条哥有一个 nnn 个节点的树,树根编号为 111

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

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

输入描述

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

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

1≤n≤105,1≤k≤109,1≤u,v≤n1 \leq n \leq 10^5, 1\leq k\leq 10^9, 1\leq u,v\leq n1n105,1k109,1u,vn

输出描述

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

样例

输入

4 2
1 2
1 3
1 4

输出

7

说明

样例解释

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

题解:树形DFS

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

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

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

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

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

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

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

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

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

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

注意:本题数据范围较大ansansans变量需要取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星限定卡的概率均为 p2\frac{p}{2}2p ,抽到非5星卡的概率为 1−p1-p1p

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

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

输入描述

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

输出描述

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

样例

输入

0.001

输出

129.1649522

题解:树形DP

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

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

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

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

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

状态方程定义

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

状态初始化

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

状态转移方程

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

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

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

特殊情况i=90i=90i=90的时候,ppp为固定值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位
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值