Skip to main content

美团2024届春招笔试第一场编程真题

·814 words·4 mins
WFUing
Author
WFUing
A graduate who loves coding.
Table of Contents
  • 题目链接:https://www.nowcoder.com/exam/company?currentTab=recommand&jobId=100&selectStatus=0&tagIds=179

4.删除区间
#

#include <iostream>
#include <cmath>
using namespace std;
const int N = 1e5;
int n, a[N+5];
// two为前i个数中2的个数,five为前i个数中5的个数
int two[N+5], five[N+5];

int get(int i, int j){
// 即2.1.1方案,求出去掉区间[i,j]后乘积末尾 0的个数
    return min(two[n]-two[j]+two[i-1], five[i-1]+five[n]-five[j]);
}
int main() {
    int k; cin >> n >> k;
    for(int i=1; i<=n; i++) cin >> a[i];
    for(int i=1; i<=n; i++){
    	// 求因子2,5的个数
        while(a[i]%2==0) two[i]++, a[i]/=2;
        while(a[i]%5==0) five[i]++, a[i]/=5;
        // 维护前缀和
        two[i] += two[i-1];
        five[i] += five[i-1];
    }
    
    int l = 0,  r = 0;// 记录上一次删除区间,防止重复相加
    long long ans = 0;
    for(int i=1, j=1; i<=n && j<=n; ){
        j = max(j, i);// 当 i右移超过j后,j不能比 i小,所以需要更新一下
        while(j <=n && get(i, j) >= k) j++;//当剩余区间值不小于k,就不断向右移
        ans += 1LL*(j-i)*(j-i+1)/2;// 删除方案即该区间的等差数列公式
        if(r >= i) ans -= 1LL*(r-i)*(r-i+1)/2; 
        //如果上一个区间[l,r]的右端点不小于本次区间[i,j]的左端点,则产生重复需要删去 [i, r]方案数
        l = i, r = j;//上次删除区间更新为[i,j]
        while(i <= j && get(i, j)<k) i++;//右移i,直到再次可以删除 或 左右边界重合 停止
    }
    cout << ans;
}

5.小美的朋友关系
#

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

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

事件共有 2 种:

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

注:介绍可以有多层,比如 2 号把 1 号介绍给 3 号,然后 3 号再把 1 号介绍给 4 号,这样 1 号和 4 号就认识了。

输入描述:

第一行输入三个正整数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"。

输入例子:
5 3 5
1 2
2 3
4 5
1 1 5
2 1 3
2 1 4
1 1 2
2 1 3
输出例子:
Yes
No
No
例子说明:
第一次事件,1 号和 5 号本来就不是朋友,所以无事发生。
第二次事件是询问,1 号和 3 号可以通过 2 号的介绍认识。
第三次事件是询问,显然 1 号和 4 号无法互相认识。
第四次事件,1 号和 2 号淡忘了。
第五次事件,此时 1 号无法再经过 2 号和 3 号互相认识了。

解题时并没有做出来,

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt(), m = in.nextInt(), q = in.nextInt();
        Set<List<Integer>> relationship = new HashSet<>();
        UnionFind union = new UnionFind(n + 1);
        int[][] events = new int[q][4];
        for (int i = 0; i < m; i++) {
            int u = in.nextInt(), v = in.nextInt();
            relationship.add(Arrays.asList(Math.min(u, v), Math.max(u, v)));
            union.parent[u]=u;
            union.parent[v]=v;
        }
        for (int i = 0; i < q; i++) {
            events[i][3] = Integer.MAX_VALUE;
        }
        for (int i = 0; i < q; i++) {
            int op = in.nextInt(), u = in.nextInt(), v = in.nextInt();
            events[i][0] = op;
            events[i][1] = u;
            events[i][2] = v;
            if (op == 1) {
                if (relationship.contains(Arrays.asList(Math.min(u, v), Math.max(u, v)))) {
                    relationship.remove(Arrays.asList(Math.min(u, v), Math.max(u, v)));
                    events[i][3] = Math.min(events[i][3], i);
                }
            }
        }
        for (List<Integer> relation : relationship) {
            union.union(relation.get(0), relation.get(1));
        }

        List<String> ans = new ArrayList<>();

        for (int i = events.length - 1; i >= 0; i--) {
            if (events[i][0] == 1) {
                if ( events[i][3] == i)
                    union.union(events[i][1], events[i][2]);
            } else {
                if(union.parent[events[i][1]]==0) union.parent[events[i][1]] = events[i][1];
                if(union.parent[events[i][2]]==0) union.parent[events[i][2]] = events[i][2];
                ans.add(union.query(events[i][1], events[i][2]) ? "Yes" : "No");
            }
        }

        for (int i = ans.size() - 1; i >= 0; i--) {
            System.out.println(ans.get(i));
        }
    }

    public static class UnionFind {
        public int[] parent;
        public UnionFind(int N) {
            parent = new int[N];
            // for (int i = 0; i < N; i++) {
            //     parent[i] = i;
            // }
        }

        public int find(int v) {
            if (v != parent[v]) {
                parent[v] = find(parent[v]);
            }
            return parent[v];
        }

        public void union(int v1, int v2) {
            int f1 = find(v1);
            int f2 = find(v2);
            if (f1 != f2) {
                parent[f2] = f1;
            }
        }

        public boolean query(int i, int j) {
            return find(i) == find(j);
        }

        public void out() {
            for (int i = 0; i < parent.length; i++) {
                System.out.print(parent[i] + " ");
            }
            System.out.println();
        }
    }
}

一直报错

后来看的别人的题解

#include <iostream>
#include <set>
#include <vector>
#include <map>
using namespace std;
const int N = 1e5;
using PII = pair<int,int>;

int n, m, q;
map<int,int>f;// 并查集父亲数组
struct node{
    int op, u, v;
}ord[N+5];// 新的操作数组

int find(int x){// 路径压缩
    while(f[x] != x) x = f[x] = f[f[x]];
    return x;
}
void merge(int u,int v){// 并查集合并
    int fa = find(u);
    int fb = find(v);
    f[fb] = fa;
}
int main() {
    cin >> n >> m >> q;
    set<PII>st;// 关系集合

    for(int i=0, u, v; i<m; i++){
        cin >> u >> v;
        st.insert({u, v}); // u, v放进关系集合中
        f[u] = u, f[v] = v;// 把出现的结点父节点设置为自己
    }

    int num = 0;// 新的操作数组长度
    for(int i=0; i<q; i++){
        int op, u, v; cin >> op >> u >> v;
        //如果是查询操作,可以直接存入
        // 如果是删除操作,需要判断原关系集合中是否存在
        if(op == 1){
        	// 可能是 {u,v} 形式存储
            if(st.find({u, v}) != st.end()) st.erase({u, v});
            // 可能是 {v,u} 形式存储
            else if(st.find({v, u}) != st.end()) st.erase({v, u});
            // 如果不存在直接跳过,不储存此次删除操作
            else continue; 
        }
        // 存入新的操作数组中
        ord[num++] = {op, u, v};
    }
    // 删除之后,剩余关系集合就是没有涉及到的,也是最终的并查集
    for(auto [u,v]:st) merge(u, v);

    vector<bool>ans;// 存储答案
    for(int i=num-1; i>=0; i--){// 倒着重新进行操作
        int op = ord[i].op, u = ord[i].u, v = ord[i].v;
        if(op == 1) merge(u, v);// 如果是删除操作,反过来进行合并
        else{
            // 当 f[u] = 0时,就是第一次出现该节点,需要初始化f[i]=i,方便进行路径压缩
            if(!f[u]) f[u] = u;
            if(!f[v]) f[v] = v;
            ans.emplace_back(find(u) == find(v));// 查询操作,就储存答案
        }
    }
	//因为是倒着遍历操作的,所以答案是倒着存储的
    for(int i=ans.size()-1; i>=0; i--) 
        if(ans[i]) cout << "Yes" << endl;
        else cout << "No" << endl;
}