美团2024年春招第一场笔试【技术】

小美的平衡矩阵

题目描述

小美拿到了一个 \(n \times n\) 的矩阵,其中每个元素是 \(0\) 或者 \(1\)。小美认为一个矩形区域是完美的,当且仅当该区域内 \(0\) 的数量恰好等于 \(1\) 的数量。现在,小美希望你回答有多少个 \(i \times i\) 的完美矩形区域。你需要回答 \(1 \leq i \leq n\) 的所有答案。

输入描述

第一行输入一个正整数 \(n\),代表矩阵大小。
接下来的 \(n\) 行,每行输入一个长度为 \(n\)\(01\) 串,用来表示矩阵,\(1\leq n \leq 200\)

输出描述

输出 \(n\) 行,第 \(i\) 行输出 \(i \times i\) 的完美矩形区域的数量。

解题思路

通过二维前缀和可快速得到一个矩形区域内的 \(0\)\(1\) 的个数。

\(dp[i][j]\) = 以 \((1,1)\) 为左上角,\((i,j)\) 为右下角的矩形中 \(1\) 的个数,则有如下递推式:

  • \(dp[i][j] = dp[i-1][j] + dp[i][j-1] -dp[i-1][j-1]\)

对于一个以 \((i,j)\) 为左上角、长为 \(k\) 的矩形,其中 \(1\) 的个数为:

  • \(dp[i + k][j + k] - dp[i + k][j - 1] - dp[i - 1][j + k] + dp[i - 1][j - 1]\)

代码实现

const int n = 2e2 + 5;
int dp[n][n];
char s[n];

int main() {
    int n;
    scanf("%d", &n);
    // 输入、计算二维前缀和
    for (int i = 1; i <= n; i++) {
        scanf("%s", s + 1);
        for (int j = 1; j <= n; j++)
            dp[i][j] = dp[i][j - 1] + dp[i - 1][j] - dp[i - 1][j - 1] + (s[j] == '1');
    }
    // mp[i] = 大小为 i*i 的矩形的数量
    unordered_map<int, int> mp;
    // 枚举每个大小为 i*i 的矩形
    for (int i = 1, t; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            for (int k = 1; i + k <= n && j + k <= n; k += 2) {
                t = dp[i + k][j + k] - dp[i + k][j - 1] - dp[i - 1][j + k] + dp[i - 1][j - 1];
                if (t * 2 == (k + 1) * (k + 1))mp[k + 1]++;
            }
        }
    }
    // 输出每个答案
    for (int i = 1; i <= n; i++)
        printf("%d\n", mp[i]);
    return 0;
}

时间复杂度:\(O(n^3)\)

空间复杂度:\(O(n^2)\)

小美的数组询问

题目描述

小美拿到了一个由正整数组成的数组,但其中有一些元素是未知的(用 \(0\) 来表示)。现在小美想知道,如果那些未知的元素在区间 \([l,r]\) 范围内随机取值的话,数组所有元素之和的最小值和最大值分别是多少?共有 \(q\) 次询问。

输入描述

第一行输入两个正整数 \(n,q\),代表数组大小和询问次数。

第二行输入 \(n\) 个整数 \(a_i\) ,其中如果输入的 \(a_i\)\(0\),那么说明 \(a_i\) 是未知的。

接下来的 \(q\) 行,每行输入两个正整数 \(l,r\),代表一次询问。

  • \(1\leq n,q \leq 10^5\)
  • \(0 \leq a_i \leq 10^9\)
  • \(1\leq l \leq r \leq 10^9\)

输出描述

输出 \(q\) 行,每行输出两个正整数,代表所有元素之和的最小值和最大值。

解题思路

\(l\) 得到最小值,选 \(r\) 得到最大值。

代码实现

typedef long long ll;

int main() {
    int n, q, cnt = 0;
    ll x, l, r, sum = 0;
    scanf("%d%d", &n, &q);
    while (n--) {
        scanf("%lld", &x);
        x == 0 ? cnt++ : sum += x;
    }
    while (q--) {
        scanf("%lld%lld", &l, &r);
        printf("%lld %lld\n", sum + l * cnt, sum + r * cnt);
    }
    return 0;
}

时间复杂度:\(O(n)\)

空间复杂度:\(O(1)\)

小美的 MT

题目描述

\(MT\) 是美团的缩写,因此小美很喜欢这两个字母。现在小美拿到了一个仅由大写字母组成字符串,她可以最多操作 \(k\) 次,每次可以修改任意一个字符。小美想知道,操作结束后最多共有多少个 \(M\)\(T\) 字符?

输入描述

第一行输入两个正整数 \(n,k\),代表字符串长度和操作次数。

第二行输入一个长度为 \(n\) 的、仅由大写字母组成的字符串。

  • \(1\leq k \leq n \leq 10^5\)

输出描述

输出操作结束后最多共有多少个 \(M\)\(T\) 字符。

解题思路

尽可能修改每个不是 \(M\)\(T\) 的字符。

代码实现

int main() {
    int n, k, cnt = 0;
    char c;
    scanf("%d%d\n", &n, &k);
    while (n--) {
        scanf("%c", &c);
        if (c == 'M' || c == 'T')cnt++;
        else if (k > 0)cnt++, k--;
    }
    printf("%d", cnt);
    return 0;
}

时间复杂度:\(O(n)\)

空间复杂度:\(O(1)\)

小美的朋友关系

题目描述

小美认为,在人际交往中,但是随着时间的流逝,朋友的关系也是会慢慢变淡的,最终朋友关系就淡忘了。

现在初始有一些朋友关系,存在一些事件会导致两个人淡忘了他们的朋友关系。小美想知道某一时刻中,某两人是否可以通过朋友介绍互相认识?

事件共有 \(2\) 种:

  • \(1\ u\ v\):代表编号 \(u\) 的人和编号 \(v\) 的人淡忘了他们的朋友关系。
  • \(2\ u\ v\):代表小美查询编号 \(u\) 的人和编号 \(v\) 的人是否能通过朋友介绍互相认识。

输入描述

第一行输入三个正整数 \(n,m,q\),代表总人数,初始的朋友关系数量,发生的事件数量。

接下来的 \(m\) 行,每行输入两个正整数 \(u,v\),代表初始编号 \(u\) 的人和编号 \(v\) 的人是朋友关系。

接下来的 \(q\) 行,每行输入三个正整数 \(op,u,v\),含义如题目描述所述。

  • \(1\leq n \leq 10^9\)
  • \(1\leq m,q \leq 10^5\)
  • \(1\leq u,v \leq n\)
  • \(1\leq op \leq 2\)

保证至少存在一次查询操作。

输出描述

对于每次 \(2\) 号操作,输出一行字符串代表查询的答案。如果编号 \(u\) 的人和编号 \(v\) 的人能通过朋友介绍互相认识,则输出"Yes"。否则输出"no"。

解题思路

  1. \(n\) 的规模超过 \(10^6\),需要离散化。
  2. 通过并查集可快速判断两人是否为朋友关系。
  3. 并查集不方便删除,故采用反向并查集。对于每个淡忘操作 \((u,v)\),若初始存在 关系\((u,v)\),则此操作前均存在关系 \((u,v)\),则此操作后均不存在关系 \((u,v)\)。因此,反向顺序进行操作,将删除转为添加,将结果正向输出即为答案。

代码实现

const int n = 1e5 + 5;

// 并查集
int p[n];
// 初始关系、关系记录
set<pair<int, int>> irs, rs;
// 操作
int ops[n][3];
// 离散化
int idx = 0;
unordered_map<int, int> mp;

/**
 * 将编号x转换为离散化后的值
 */
int dsz(int x) {
    if (mp[x] == 0)mp[x] = ++idx;
    return mp[x];
}

/**
 * 找到节点x所在集合的根节点
 */
int find(int x) {
    if (p[x] != x)p[x] = find(p[x]);
    return p[x];
}

/**
 * 合并节点u和节点v所在两个集合
 */
void unite(int u, int v) {
    int pu = find(u), pv = find(v);
    if (pu != pv)p[pu] = pv;
}

int main() {
    int n, m, q, op, u, v;
    scanf("%d%d%d", &n, &m, &q);
    iota(p, p + n, 0);
    // 关系记录 = 初始关系
    while (m--) {
        scanf("%d%d", &u, &v);
        u = dsz(u), v = dsz(v);
        if (u > v)swap(u, v);
        irs.insert({u, v});
        rs.insert({u, v});
    }
    // 记录操作、关系记录 = 初始关系 - 淡忘关系
    for (int i = 0; i < q; i++) {
        scanf("%d%d%d", &op, &u, &v);
        u = dsz(u), v = dsz(v);
        if (u > v)swap(u, v);
        ops[i][0] = op, ops[i][1] = u, ops[i][2] = v;
        if (op == 1)rs.erase({u, v});
    }
    // 根据关系记录建立并查集
    for (auto &[a, b]: rs) unite(a, b);
    // 从前往后是删除关系,从后往前是添加关系
    bool r[q + 1];
    memset(r, 0, sizeof r);
    for (int i = q - 1; i >= 0; i--) {
        if (ops[i][0] == 2)r[i] = find(ops[i][1]) == find(ops[i][2]);
            // 初始为朋友关系的两人才能淡化朋友关系
        else if (irs.find({ops[i][1], ops[i][2]}) != irs.end())unite(ops[i][1], ops[i][2]);
    }
    // 输出结果
    for (int i = 0; i < q; i++) {
        if (ops[i][0] == 2)printf("%s\n", r[i] ? "Yes" : "no");
    }
    return 0;
}

时间复杂度:\(O(m+q)\)

空间复杂度:\(O(m+q)\)

小美的区间删除

题目描述

小美拿到了一个大小为 \(n\) 的数组,她希望删除一个区间后,使得剩余所有元素的乘积末尾至少有 \(k\)\(0\)。小美想知道,一共有多少种不同的删除方案?

输入描述

第一行输入两个正整数 \(n,k\)

第二行输入 \(n\) 个正整数 \(a_i\),代表小美拿到的数组。

  • \(1\leq n,k \leq 10^5\)
  • \(1\leq a_i \leq 10^9\)

输出描述

一个整数,代表删除的方案数。

解题思路

一个 \(2\) 和 一个 \(5\) 相乘可得到一个 \(0\)

代码实现

const int n = 1e5 + 5;

// s2[i] = a[1..i]中包含2的个数
// s5[i] = a[1..i]中包含5的个数
int s2[n], s5[n];

/**
 * 统计 x 中包含的 2 和 5 的个数
 */
pair<int, int> count25(int x) {
    int c2 = 0, c5 = 0;
    while (!(x & 1))c2++, x /= 2;
    while (x % 5 == 0)c5++, x /= 5;
    return {c2, c5};
}

int main() {
    int n, k, x;
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &x);
        auto [c2, c5] = count25(x);
        s2[i] = c2 + s2[i - 1], s5[i] = c5 + s5[i - 1];
    }
    int l, r;
    // 检查 l 和 r 是否不符合要求
    auto check = [&] {
        return s2[r] - s2[l - 1] > s2[n] - k || s5[r] - s5[l - 1] > s5[n] - k;
    };
    long long res = 0;
    // 枚举每类以 l 为左边界的区间
    for (l = 1; l <= n; l++) {
        // 找到以 l 为左边界且满足题目条件的最大区间
        int i = l, j = n;
        while (i < j) {
            r = (i + j + 1) >> 1;
            if (check())j = r - 1;
            else i = r;
        }
        r = i;
        // 若[l,r] 满足条件,则 [l,r-1] 也满足条件,类推
        if (!check())res += r - l + 1;
    }
    printf("%lld", res);
    return 0;
}

时间复杂度:\(O(nlog(n))\)

空间复杂度:\(O(n)\)

END

文章文档:公众号 字节幺零二四 回复关键字可获取本文文档。

题目来源:美团2024年春招第一场笔试【技术】

文章声明:题目来源 牛客 平台,如有侵权,请联系删除!

posted @ 2024-06-26 07:34  字节幺零二四  阅读(184)  评论(0)    收藏  举报