[算法]2026年3月14日米哈游校招算法笔试题题解

1.行列相等

1.1.题目

描述

给定一个 n 行 m 列的整数矩阵 a,请统计满足以下条件的行列对数量:- 第 i 行所有元素之和恰好等于第 j 列所有元素之和。

输入

在一行上输入两个整数 n,m (1≤n,m≤1e6;n×m≤1e6),表示矩阵的行数和列数;此后 n 行,每行输入 m 个整数 ai,1​,ai,2​,...,ai,m​ (0≤ai,j​≤1e9),表示矩阵中各元素。

输出

输出一个整数,表示满足条件的行列对数量。

用例输入 1

复制代码
3 3
1 1 1
1 1 1
1 1 1

用例输出 1

复制代码
9

1.2.题解

  • 哈希表计数 :用unordered_map存储键为和值 ,值为pair<行出现次数, 列出现次数>
  • 先算行:遍历所有行,计算行和,更新哈希表中行的计数
  • 再算列:遍历所有列,计算列和,更新哈希表中列的计数
  • 统计答案 :遍历哈希表,累加行计数 × 列计数得到总数量

**时间复杂度:**O(n*m)

**空间复杂度:**O(n*m)

AC代码:

cpp 复制代码
#include <iostream>
#include <bits/stdc++.h>
using namespace std;

#define ll long long


int main() {
    ll n,m;
    cin>>n>>m;
    unordered_map<ll,pair<ll,ll>> sumMapCnts;
    vector<vector<ll>> ma(n,vector<ll>(m));

    //计算每行的和并统计到哈希表
    for(ll i=0;i<n;++i)
    {
        ll sum=0;
        for(ll j=0;j<m;++j)
        {
           cin>>ma[i][j];
           sum += ma[i][j];
        }
       	++sumMapCnts[sum].first;
    }
    

    //计算每列的和并统计到哈希表
    for(ll j=0;j<m;++j)
    {
        ll sum=0;
        for(ll i=0;i<n;++i)
        {
            sum += ma[i][j];
        }
        ++sumMapCnts[sum].second;
    }
    
    ll ans=0;//保存答案
    for(const auto &[sum,p]:sumMapCnts)
    {
        ans += p.first*p.second;
    }
    cout<<ans<<endl;
    
    return 0;
}

2.乱翘的数组hard

2.1.题目

描述

对于给定的长度为 n 的数组 {a1​,a2​,...,an​},我们定义"翘数"为同时严格大于或小于左右相邻数的数字。形式化的讲,对于第 i (1<i<n) 个整数 ai​,它被称作"翘数",当且仅当满足 ai−1​ < ai​ > ai+1​ 或 ai−1​> ai​ < ai+1​。 若一个数组中,所有的满足 i∈(1,n) 的数字 ai​ 均为"翘数",且任意相邻的两个元素 aj​,aj+1​ (1≤j<n) 都不相等,则称该数组为"乱翘的数组"。 现在,对于给定的初始数组,计算最少需要从原数组中删除的数字个数,使得剩余数字按原相对顺序拼接成的新数组是一个"乱翘的数组"。

输入

第一行输入一个整数 n (3≤n≤2×1e5) 代表数组中的元素数量。 第二行输入 n 个整数 a1​,a2​,...,an​ (−1e7≤ai​≤1e7) 代表数组元素。

输出

在一行上输出一个整数,代表最少需要删除的数字个数。

用例输入 1

复制代码
7
1 3 1 4 5 2 0

用例输出 1

复制代码
2

用例输入 2

复制代码
3
2 2 2

用例输出 2

复制代码
2

2.2.题解

把数组作为折线图来看,

其实就是要删除除了顶峰和谷底的所有元素

如下图所示,每个绿色圆圈代表一个需要保留的数,非绿色圆圈的数都是要被删除的

那如果有连续相等的数呢?

那就需要视情况提前删除掉一些,因为连续相等的数不符合翘数的要求,并且提前删除代码也好写

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

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

AC代码:

cpp 复制代码
#include <iostream>
#include <bits/stdc++.h>
using namespace std;

#define ll long long

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    ll n;
    cin>>n;
    vector<ll> arr;
    ll ans=0;//ans为最终需要删除的数
    for(ll i=0;i<n;++i)
    {
        ll num;cin>>num;
        if(!arr.empty() && arr.back()==num) ++ans;
        else arr.emplace_back(num);
    }
    
    ll cnt=0; //cnt统计需要保留的数,即上图绿色圆圈代表的数
    for(ll i=0;i<arr.size();++i)
    {
        //如果是峰顶
        if((i-1>=0 && arr[i-1]<arr[i] || i-1<0) && (i+1<arr.size() && arr[i]>arr[i+1] || i+1>=arr.size())) ++cnt;
        //如果是谷底
        else if((i-1>=0 && arr[i-1]>arr[i] || i-1<0) && (i+1<arr.size() && arr[i]<arr[i+1] || i+1>=arr.size())) ++cnt;
    }
    ans += arr.size()-cnt;
    cout<<ans<<endl;
    
    return 0;
}

3.树上异或路径

3.1.题目

描述

给你一棵有 n 个节点的无向树。每条边有一个非负整数权值 w。请计算这棵树上所有简单路径的异或和之和。注意,本题中树为无向图,端点 (u,v) 与 (v,u) 视为同一路径,仅统计一次。说明:路径指的是在树上选择两个节点作为端点的简单路径。当两个端点相同的时候,路径长度为 0,其异或和为 0。

输入

每个测试文件均包含多组测试数据。第一行输入一个整数 T (1≤T≤2×1e5) 表示数据组数。除此之外,保证单个测试文件的 n 之和不超过 5×1e5。每组测试数据的格式如下:第一行输入一个整数 n (1≤n≤2×1e5),表示节点数量;此后 n−1 行,每行输入三个整数 u,v,w (1≤u,v≤n;0≤w≤1e9),表示一条连接 u 与 v 的边及其权值。保证给出的是一棵树。

输出

对于每一组测试数据,新起一行输出一个整数,表示所有简单路径的异或和之和。若结果可能很大,请将答案对 1e9+7 取模后输出。

用例输入 1

复制代码
2
3
1 2 1
2 3 2
4
1 2 0
2 3 0
3 4 7

用例输出 1

复制代码
6
21

3.2.题解

首先假设节点1是整棵树的根,再开辟一个大小为n的数组rootXor,

其中rootXor[i]表示从根节点到i节点的简单路径上经过的所有权值的异或和

计算rootXor可以通过dfs和邻接表来算

此时任意两个节点u,v的简单路径的异或和都可以通过rootXor来计算,

等于rootXor[u] ^ rootXor[v] (因为异或有一个性质,a^a=0).

那么此时整个问题就变为:从rootXor数组中任意挑出两个不相同的位置i,j(i<j)的元素组成元素对,求所有元素对的异或结果的和

用数学公式表示为:

如果直接暴力计算时间复杂度就是O(n^2),肯定不行

此时异或还有一个性质,即每一个二进制位的计算互不影响,

所以我们可以分开求每一个二进制位对答案S的贡献,再累加求和即可;

那么如何求某一个二进制位对答案S的贡献?

我们可以统计rootXor数组中出有多少个数在这一二进制位上的值为1(计为cnt1),有多少个数在这一二进制位上的值为0(计为cnt0);

然后 cnt1*cnt0*这一个二进制位在十进制内的权值 即为该二进制位对答案S的贡献(cnt1*cnt0 是求 在这个二进制位上进行异或计算会出现多少次1)

至此,思路完毕

**时间复杂度:**O(nlogU),n为点的个数,U为权重最大值的二进制位数

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

AC代码:

cpp 复制代码
#include <iostream>
#include <bits/stdc++.h>

#define ll long long

using namespace std;
int main() {
    const ll mod=1e9+7;
   	ll t;
    cin>>t;
    while(t--)
    {
        ll n;
        cin>>n;
        //邻接表,ad[i]:保存所有与i节点有边连接的点
        vector<vector<pair<ll,ll>>> ad(n);
        for(int i=1;i<=n-1;++i)
        {
            ll u,v,w;
            cin>>u>>v>>w;
            --u,--v;//作用是让节点从0开始计数,而不是题目的从开始
            ad[u].emplace_back(v,w);
            ad[v].emplace_back(u,w);
        }

        //rootXor[i]:从根节点到i节点的简单路径上经过的所有权值的异或和
        vector<ll> rootXor(n);
        
        //通过dfs计算rootXor内所有元素的值,时间复杂度为O(n)
        function<void(ll,ll)> dfs;
        dfs=[&](ll pa,ll cur)
        { 
            for(auto &[child,w]:ad[cur])
            {
                if(pa!=child)
                {
                    rootXor[child]=rootXor[cur]^w;
                    dfs(cur,child);
                }
            }
        };
        dfs(-1,0);
        
        ll ans=0; //保留答案
        for(ll i=0;i<=30;++i) 
        {
            //计算cnt0和cnt1,含义如上述文字
            ll cnt0=0,cnt1=0;
            for(auto &e:rootXor)
            {
                if((e>>i)&1) ++cnt1;
                else ++cnt0;
            }

            //累加和到ans上
            ans=(ans%mod+(((1ll<<i)*cnt0*cnt1)%mod)%mod)%mod;
        }
        
       
        cout<<ans<<endl;
    }
    
    return 0;
}
相关推荐
AC__dream1 小时前
2024秋招-字节跳动-算法岗笔试
数据结构·算法
一叶落4381 小时前
LeetCode 151. 反转字符串中的单词(C语言)【双指针 + 字符串处理】
c语言·数据结构·算法·leetcode
_olone1 小时前
牛客每日一题:刷题统计(Java)
java·算法·容斥原理·牛客
无敌憨憨大王1 小时前
DFS(深搜)
算法·深度优先·图论
junnhwan1 小时前
LeetCode Hot 100——栈
java·数据结构·算法·leetcode·hot 100
sqyno1sky1 小时前
代码动态生成技术
开发语言·c++·算法
superior tigre1 小时前
347 前k个高频元素
数据结构·算法·leetcode
2401_853576501 小时前
C++中的策略模式变体
开发语言·c++·算法
m0_528174452 小时前
C++中的策略模式实战
开发语言·c++·算法
MicroTech20252 小时前
突破非幺正演化难题:MLGO微算法科技研发概率量子算法实现虚时间演化新路径
科技·算法·量子计算