美团2026届秋招8.16笔试个人题解

美团2026届秋招8.16笔试个人题解

T1.非负整数数组

题目描述

小美有一个长度为 $ n $ 的数组 $ {a_1, a_2, \dots, a_n} $,她希望构造一个非负整数 $ c $,满足 $ c $ 的二进制位数不超过数组中最大值的二进制位数(特别地,0 的二进制位数定义为 1)。

随后,她可以对数组重复进行以下操作,以使所有元素的总和最大:

  • 选择一个下标 $ i $,将 $ a_i $ 修改为 $ a_i \mid c $(按位或),同时将 $ c $ 修改为 $ a_i & c $(按位与)。

在使数组元素总和达到最大值的前提下,要求初始的 $ c $ 尽可能小。

请输出最大总和以及对应的最小初始 $ c $。


说明

  • 按位或(|):对两个整数的二进制表示的每一位进行逻辑或操作。
  • 按位与(&):对两个整数的二进制表示的每一位进行逻辑与操作。
  • 操作可以执行任意多次,每次选择一个下标 $ i $。

输入描述

每个测试文件包含多组测试数据。

第一行输入一个整数 \(T\)($ 1 \leq T \leq 1000 $),表示测试数据的组数。

对于每组测试数据,输入格式如下:

  • 第一行输入一个整数 \(n\)($ 1 \leq n \leq 500 $),表示数组的长度;
  • 第二行输入 \(n\) 个整数 \(a_1, a_2, \ldots, a_n\)($ 0 \leq a_i < 2^{30} $),表示数组 $ a $ 的元素。

题目分析

这两个数学运算组合起来,本质上就是,每次操作都会从\(x\)当中取出一个1分给\(a_i\),那么我们最终的\(x\),其实就是所有\(a_i\)所缺少的1构成的总和。

python代码

import sys

LENGTH = 30
UPPER = (1 << LENGTH) - 1

def len_of_bits(x):
    ans: int = 0
    while x:
        ans += 1
        x >>= 1
    if not ans:
        ans = 1
    return ans

def main():
    input = sys.stdin.read().strip('\r\n\t ').split()
    it = iter(input)
    T = int(next(it))
    ans = []
    for _ in range(T):
        n = int(next(it))
        arr = [int(next(it)) for _ in range(n)]
        s = sum(arr)
        _max = max(arr)
        len = len_of_bits(_max)
        mask = (1 << len) - 1
        upper = UPPER
        for ai in arr:
            upper &= ai
        upper &= mask
        x: int = mask ^ upper # 通过和mask异或,来翻转0和1
        s += x
        ans.append(f"{s} {x}")
    print("\n".join(ans))

if __name__ == "__main__":
    main()

CPP代码

#include <algorithm>
#include <iostream>
#include <cstdio>

constexpr int N = 505, LEN = 30;
constexpr int UPPER = (1 << LEN) - 1;
int arr[N], T;

int len_of_bits(int x) {
    int ans = 0;
    while (x) {
        ans++;
        x>>=1;
    }
    return ans?ans:1;
}

int main() {
    freopen("input.txt", "r", stdin);
    scanf("%d", &T);
    int len;
    while (T--) {
        scanf("%d", &len);
        int _max = -1, sum = 0;
        for (int i = 1; i <= len; i++) {
            scanf("%d", &arr[i]);
            sum += arr[i];
            _max = std::max(_max, arr[i]);
        }
        int len = len_of_bits(_max);
        int upper = UPPER, mask = (1 << len) - 1;
        for (int i = 1; i <= len; i++) {
            upper &= arr[i];
        }
        upper &= mask;
        int x = mask ^ upper;
        sum += x;
        printf("%d %d\n", sum, x);
    }
    return 0;
}

T2.奇幻王国魔幻石

key: 分层图的理解

题目描述

在一个奇幻王国里,有 \(n\) 个城市(编号依次为1到n)和 \(m\) 条双向道路,第 \(i\) 条道路连接城市 \(u_i\)\(v_i\),基础通行时间为正整数 \(w_i\)。此外,王国中每个城市都存在1个中枢魔法石,每个魔法石有一个能量值,非负能量值的魔法石称之为「坏魔法石」,负数能量值的魔法石称之为「好魔法石」,坏魔法石会增加通行所需时间,好魔法石会减少通行所需时间(增加和减少的时间即为能量值的多少),如果好魔法石足够强力,甚至可以实现时间倒流。

坏魔法石将会强制生效,导致基础通行时间增加,无法被控制。

好魔法石可以控制,选择是否生效,但使用好魔法石的总次数存在限制,第 \(k\) 次使用好魔法石生效以后,之后将无法利用任何城市的好魔法石来减少通行时间。换句话说,单次行程中好魔法石总使用次数不超过 \(k\) 次。

魔法石不会消失,可以多次使用。

当你从城市 \(u\) 前往城市 \(v\) 时,路径的实际通行时间计算如下:

通行时间 = 城市 \(u\) 到城市 \(v\) 的道路基础通行时间 + 城市 \(v\) 生效的魔法石能量值

请计算从城市1到城市 \(n\) 的最小实际通行时间,注意,您可以重复经过城市和道路。特别地,如果无论如何都无法到达城市 \(n\),直接输出 NO

输入描述

第一行输入三个整数 $ n, m, k $,满足:

  • $ 2 \leq n \leq 10^3 $
  • $ 1 \leq m \leq \min{2 \times 10^3, \frac{n \times (n-1)}{2}} $
  • $ 1 \leq k \leq 10^3 $

第二行输入 $ n $ 个整数 $ a_1, a_2, \ldots, a_n $,其中 $ -10^5 \leq a_i \leq 10^5 $,表示第 $ i $ 个城市中的魔法石能量。

接下来 $ m $ 行,第 $ i $ 行输入三个整数 $ u_i, v_i, w_i $,满足:

  • $ 1 \leq u_i, v_i \leq n $
  • $ u_i \neq v_i $
  • $ 1 \leq w_i \leq 10^5 $

表示城市 $ u_i $ 与城市 $ v_i $ 之间存在一条同行时间为 $ w_i $ 的路径。除此之外,保证任意两个城市之间至多存在一条道路。

注意:本题不保证图的连通性,即可能存在两个城市之间无法通过任何路径互相到达的情况。

输出描述

如果无论如何都无法到达城市\(n\),直接输出\(NO\),否则输出一个整数,表示从城市\(1\)到城市\(n\)的最少实际通行时间。

题目分析

一道非常典型的分层图问题,但是和板子题相比,\(k\)的取值比较大,因此我们不能显式地把整张分层图建出来,我们只建立一层,后续通过循环的方式来跑多层。

这道题的特殊之处在于:

因为“好魔法石”会带来负数边权,我们是不能建立完整多层图的(我们用的最短路算法是Dijkstra,如果建立出完整的分层图,那么就相当于我们在有负权边的图上跑Dijkstra,这是不符合其算法性质的),幸好题目的\(k\)给的比较大,断绝了我们把整个分层图建出来的可能,帮我们避免了一个潜在坑点。

我们通过循环模拟分层图的时候,通过cur和nxt两个distance数组滚动更新,每次nxt都要初始化为无穷,只将其中与魔法石相关的点做距离修改(如果我们直接让nxt继承自cur,那么其物理意义就是:两层图之间所有点,都相互连接;但事实上只有被魔法石影响的点之间存在跨层边)。

其他细节解释放在Python代码注释当中。

注意,cpp的优先队列默认是大根堆,而Python的heapq默认是小根堆。

Python代码

import sys
import heapq

INF = 4*10**9
n: int = 0

def DJ(edges, stones, dist):
    q = []
    vis = [False] * (n+1)
    for i in range(1, n + 1):
        if dist[i] != INF:
            heapq.heappush(q, (dist[i], i))
    while len(q):
        dis, id = heapq.heappop(q)
        if vis[id]:
            continue
        vis[id] = True
        for v, w in edges[id]:
            new_dis = dis + stones[v] + w
            if new_dis < dist[v]:
                dist[v] = new_dis
                heapq.heappush(q, (dist[v], v))

def main():
    input = sys.stdin.read().strip().split()
    it = iter(input)
    global n
    n = int(next(it))
    m = int(next(it))
    k = int(next(it))
    # stones表示魔法石分数的真实值
    stones = [0] * (n + 1)
    # one_layer_stones表示只在单层图内跑时,魔法石的分数
    one_layer_stones = stones.copy()
    for i in range(1, n + 1):
        stones[i] = int(next(it))
        one_layer_stones[i] = max(stones[i], 0)
    # 连边,每个点用一个list存储边,用终点和边权表示一条边
    edges = [[] for _ in range(n + 1)]
    for _ in range(m):
        u = int(next(it))
        v = int(next(it))
        w = int(next(it))
        edges[u].append((v, w))
        edges[v].append((u, w))

    cur = [INF] * (n+1)
    cur[1] = 0
    nxt = []
    ans = INF
    # 在原图上跑一遍
    DJ(edges, one_layer_stones, cur)
    ans = min(ans, cur[n])

    # 在分层图上跑
    for _ in range(k):
        nxt = [INF] * (n+1)
        # 初始化距离
        for u in range(1, n + 1):
            if cur[u] >= INF:
                continue
            # 跨层连边
            for v, w in edges[u]:
                if stones[v] < 0:
                    new_dis = stones[v] + cur[u] + w
                    if new_dis < nxt[v]: # 注意这里是nxt,我们在更新nxt数组
                        nxt[v] = new_dis
        # 注意缩进,DJ应该是在这个level执行
        DJ(edges, one_layer_stones, nxt)
        ans = min(ans, nxt[n])
        cur = nxt
    if ans >= INF//2:
        print("NO")
    else:
        print(ans)

if __name__ == "__main__":
    main()

为什么我们判定无法到达的条件是:
\(ans >= INF/2\)
而不是
\(ans==INF\)
因为即便图不联通,1无法到达n,n的距离也可能被与其相连的其他点更新,会略小于INF,所以我们需要在INF的基础上添加一个浮动范围

Cpp代码

在Cpp当中,我们利用下标来控制滚动数组,这样性能更好,减少了copy次数.
把存边方式改成链式前向星,提高性能。

namespace leetcode {
template <typename T> using vector = std::vector<T>;
template <typename T> using function = std::function<T>;

constexpr int N = 1e3 + 5, M = (2e3 + 2) * 2;
constexpr int INF = 1e9;

// 链式前向星
std::array<int, N> h{};
std::array<int, M> ne{}, e{}, wei{};
int cnt = 0;

int n, m, k;

// int stones[N], stones_one_layer[N];
std::array<int, N> stones{}, stones_one_layer{};
std::array<bool, N> vis;

std::array<std::array<int, N>, 2> dist;

using PII = std::pair<int, int>;

class Solution {
public:
  void solve() {
    std::freopen("input.txt", "r", stdin);
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);

    // 初始化
    for (auto &dk : dist) {
      std::fill(dk.begin(), dk.end(), INF);
    }
    dist[0][1] = 0;

    std::cin >> n >> m >> k;
    for (int i = 1; i <= n; i++) {
      std::cin >> stones[i];
      stones_one_layer[i] = std::max(0, stones[i]);
    }
    int u, v, w;
    for (int i = 1; i <= m; i++) {
      std::cin >> u >> v >> w;
      add(u, v, w);
      add(v, u, w);
    }
    function<void(std::array<int, N> &)> DJ =
        [&](std::array<int, N> &Dist) -> void {
      std::fill(vis.begin(), vis.end(), false);
      // 初始化队列
      std::priority_queue<PII, vector<PII>, std::greater<PII>> q;
      for (int i = 1; i <= n; i++) {
        if (Dist[i] != INF) {
          q.push({Dist[i], i});
        }
      }
      while (q.size()) {
        auto p = q.top();
        q.pop();
        int id = p.second, dis = p.first;
        if (vis[id]) {
          continue;
        }
        vis[id] = true;
        for (int i = h[id]; i; i = ne[i]) {
          int _to = e[i];
          int new_dis = dis + stones_one_layer[_to] + wei[i];
          if (Dist[_to] > new_dis) {
            Dist[_to] = new_dis;
            q.push({Dist[_to], _to});
          }
        }
      }
    };
    int now = 0, nxt;
    int ans = INF;
    // 原图跑一遍DJ
    DJ(dist[0]);
    ans = std::min(dist[0][n], ans);
    while (k--) {
      nxt = now ^ 1;
      std::fill(dist[nxt].begin(), dist[nxt].end(), INF);
      // 在层之间连边
      for (int i = 1; i <= n; i++) {
        if (dist[now][i] >= INF) {
          continue;
        }
        for (int j = h[i]; j; j = ne[j]) {
          int _to = e[j];
          if (stones[_to] >= 0)
            continue;
          int new_dis = dist[now][i] + stones[_to] + wei[j];
          if (new_dis < dist[nxt][_to]) {
            dist[nxt][_to] = new_dis;
          }
        }
      }
      // 分层图跑DJ
      DJ(dist[nxt]);
      ans = std::min(ans, dist[nxt][n]);
      // 切换层
      now = nxt;
    }
    if (ans >= INF / 2) {
      std::cout << "NO\n";
    } else {
      std::cout << ans << "\n";
    }
  }

private:
  inline void add(int u, int v, int w) {
    ne[++cnt] = h[u];
    e[cnt] = v;
    h[u] = cnt;
    wei[cnt] = w;
  }
};
} // namespace leetcode

这道题比较玄学的地方在于,全局变量应该是默认为0才对,但是一开始连续运行了很多次,cnt的初始值都不是0,最后手动赋值0,结果正确。

T3. 奇幻王国魔法石

key: 树状数组是建立在值域上的,注意在值域上做修改

题目描述

对于给定的由 $ n $ 个整数组成的数组 $ {a_1, a_2, \ldots, a_n} $,计算其中有多少个三元组 $ (i, j, k) $ 满足:

  • $ 1 \leq i < j < k \leq n $
  • $ a_i > a_k > a_j $

即计算:

\[\sum_{1 \leq i < j < k \leq n} [a_i > a_k > a_j] \]

其中 \([P]\) 为艾弗森括号(Iverson bracket),定义如下:

  • \([P] = 1\) 如果 \(P\) 为真
  • \([P] = 0\) 如果 \(P\) 为假

请编写一个函数,计算并返回满足条件的三元组的数量。

示例

在数组 \(\{4, 1, 2, 3\}\) 中,满足条件的三元组有:

  • \((1, 2, 3)\)\(a_1 = 4 > a_3 = 2 > a_2 = 1\)
  • \((1, 2, 4)\)\(a_1 = 4 > a_4 = 3 > a_2 = 1\)
  • \((1, 3, 4)\)\(a_1 = 4 > a_4 = 3 > a_3 = 2\)

因此答案为 3。

输入描述

第一行输入一个整数 $ n $,满足 $ 1 \leq n \leq 2 \times 10^5 $,代表数组中的元素个数。

第二行输入 $ n $ 个整数 $ a_1, a_2, \ldots, a_n $,满足 $ -10^9 \leq a_i \leq 10^9 $,代表数组中的元素。

题目分析

这道题的形式就让人直接联想到树状数组,进而想到三维偏序,但是三维偏序问题当中,我们要处理的是三个相互独立的不等式,不等式涉及二元组,而这里是一个三元组,两个不等式,处理方式是不同的。
三维偏序能直接计算出符合要求的二元组个数,但是这里的不等式,我们想借助树状数组直接处理室比较棘手的,因此考虑间接求解
这道题麻烦之处在于,下标的要求是\(i<j<k\),而大小的要求则是\(a_i>a_k>a_j\),两个不等式中,j一次在中间,一次在末尾。
如果两个不等式里,j都在中间(也就是\(a_i>a_j>a_k\))的话,那么我们直接分别统计\(a_i>a_j\)以及\(a_j>a_k\),然后直接利用乘法原理相乘即可。
现在,不等式的形式虽然没有那么理想,但是上面的思路依然很有帮助:
既然我们能够轻松的计算出\(a_i>a_j>a_k\)情形下,满足要求的三元组个数,那我们还差什么条件,可以确定\(a_i>a_k>a_j\)的三元组个数呢?
我们给这两个条件,取一个“并”,可以得到总条件\(a_i > a_j and a_i > a_k\)(此时\(a_j\)\(a_k\))大小关系不确定,显然二者无交集,那我们只要先求出并集,再计算出\(a_i>a_j>=a_k\)的三元组个数,然后相减,即可得到答案。

如何计算\(a_i>a_j>=a_k\)的三元组个数?
先计算左边不等式,再计算右边不等式,相乘即可。

python代码

import sys
import bisect

def low_bit(x):
    return x & (-x)

class TreeArr:
    def __init__(self, upper):
        self.arr = [0] * (upper + 1)
        self.upper = upper

    def add(self, pos, val):
        now = pos
        while (now <= self.upper):
            self.arr[now] += val
            now += low_bit(now)

    def sum(self, pos):
        now = pos
        res = 0
        while now:
            res += self.arr[now]
            now -= low_bit(now)
        return res

def main():
    input = sys.stdin.read().strip().split()
    it = iter(input)
    n = int(next(it))
    a = [0] * (n + 1)
    for i in range(1, n + 1):
        a[i] = int(next(it))
    # 离散化
    vals = sorted(set(a))
    _max = len(vals)
    func = lambda x: bisect.bisect_left(vals, x) + 1 
    arr = [func(v) for v in a] # 注意这里是a,不是vals,把a里面大的值域映射到小的值域

    ans = 0
    # 我右边,小于等我的个数,用来计算a_j >= a_k
    less_or_equal_right = [0] * (n + 1)
    # 我左边,大于我的个数,用来计算a_i>a_j
    # 因为树状数组只能向下统计,所以这个greater_left需要通过一个减法间接得到
    # 不需要真的创建出来,可以在第二个循环逐步使用
    # greater_left = [0] * (n + 1)

    # 从后往前遍历
    backward_ta = TreeArr(_max)
    for i in range(n, 0, -1):
        cnt = backward_ta.sum(arr[i] - 1)
        less_or_equal_right[i] = backward_ta.sum(arr[i])
        ans += cnt * (cnt - 1) // 2 # 即组合数C(n, 2)
        backward_ta.add(arr[i], 1)
    
    # 然后从前往后
    forward_ta = TreeArr(_max)
    for i in range(1, n + 1, 1):
        cnt = i - 1 - forward_ta.sum(arr[i])
        ans -= cnt * less_or_equal_right[i]
        forward_ta.add(arr[i], 1)

    print(ans)


if __name__ == "__main__":
    main()

Cpp版本

namespace leetcode {
template <typename T> using vector = std::vector<T>;
template <typename T> using function = std::function<T>;

using PII = std::pair<int, int>;

constexpr int N = 2e5 + 2;

int n;
std::array<int, N> a{}, b{}, less_or_equal_right{};

inline int lowbit(int x) { return x & (-x); }
struct TreeArr {
  int upper;
  std::vector<int> arr;
  TreeArr(int _upper) : upper(_upper) { arr.resize(upper + 1); }

  int query(int pos) {
    int res = 0;
    while (pos) {
      res += arr[pos];
      pos -= lowbit(pos);
    }
    return res;
  }

  void update(int pos, int x) {
    while (pos <= upper) {
      arr[pos] += x;
      pos += lowbit(pos);
    }
  }
};

class Solution {
public:
  void solve() {
    freopen("input.txt", "r", stdin);
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
    std::cin >> n;
    for (int i = 1; i <= n; i++) {
      std::cin >> a[i];
    }
    // 离散化
    std::vector<int> tmp(a.begin(), a.end());
    std::sort(tmp.begin(), tmp.end());
    tmp.erase(std::unique(tmp.begin(), tmp.end()), tmp.end());

    for (int i = 1; i <= n; i++) {
      b[i] = (std::lower_bound(tmp.begin(), tmp.end(), a[i]) - tmp.begin()) + 1;
    }

    long long ans = 0;
    TreeArr backward_tree = TreeArr(tmp.size() + 1);
    for (int i = n; i >= 1; i--) {
      int cnt = backward_tree.query(b[i] - 1);
      less_or_equal_right[i] = backward_tree.query(b[i]);
      ans += static_cast<long long>(cnt) * (cnt - 1) / 2;
      backward_tree.update(b[i], 1);
    }
    TreeArr forward_tree = TreeArr(tmp.size() + 1);
    for (int i = 1; i <= n; i++) {
      int cnt = i - 1 - forward_tree.query(b[i]);
      ans -= static_cast<long long>(cnt) * less_or_equal_right[i];
      forward_tree.update(b[i], 1);
    }
    std::cout << ans << std::endl;
  }
};
} // namespace leetcode
posted @ 2025-08-17 05:13  Gold_stein  阅读(339)  评论(0)    收藏  举报