首页 > 试题广场 >

小美的朋友关系

[编程题]小美的朋友关系
  • 热度指数:4714 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 256M,其他语言512M
  • 算法知识视频讲解
小美认为,在人际交往中,但是随着时间的流逝,朋友的关系也是会慢慢变淡的,最终朋友关系就淡忘了。
现在初始有一些朋友关系,存在一些事件会导致两个人淡忘了他们的朋友关系。小美想知道某一时刻中,某两人是否可以通过朋友介绍互相认识?
事件共有 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"。
示例1

输入

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 号互相认识了。
这道题是这里最难的一道思路是反向并查集和离散化以及hashcode的思想
想学算法推荐看左程云老师的视频讲解很细致
import java.util.*;

import java.io.*;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static final int MAXN=200001;
    public static HashMap<Integer,Integer> map=new HashMap<>();
    public static HashSet<Integer> set=new HashSet<>();
    public static int[] father=new int[MAXN];
    public static int[][] question=new int[MAXN][3];
    public static int[][] edge=new int[MAXN][2];
    public static HashMap<Long,Integer> stringmap=new HashMap<>();
    public static int base=499999;
    public static int n;
    public static int m;
    public static int q;
    public static long tohashcode(int a,int b){
        //类似字符串hash让其自然溢出同样也是做赌狗,如果过不去可以试着换下base的值(建议换成一个大质数)
        return (long)a*base*base+b*base;
    }
    //一定要做扁平化处理不然会超时
    public static int find(int v){
        if(father[v]!=v){
            father[v]=find(father[v]);
        }
        return father[v];
    }
    public static void union(int l,int r){
        int fl=find(l),fr=find(r);
        if(fl!=fr) father[fl]=fr;
    }
    public static void main(String[] args) throws IOException{
        BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(System.out));
        String[] str=br.readLine().split(" ");
        n=Integer.parseInt(str[0]);
        m=Integer.parseInt(str[1]);
        q=Integer.parseInt(str[2]);
        for(int i=0;i<m;i++){
            str=br.readLine().split(" ");
            int V1=Integer.parseInt(str[0]),V2=Integer.parseInt(str[1]);
            edge[i][0]=Math.min(V1,V2);
            edge[i][1]=Math.max(V1,V2);
            set.add(V1);
            set.add(V2);
        }
        for(int i=0;i<q;i++){
            str=br.readLine().split(" ");
            question[i][0]=Integer.parseInt(str[0]);
            int V1=Integer.parseInt(str[1]),V2=Integer.parseInt(str[2]);
            question[i][1]=Math.min(V1,V2);
            question[i][2]=Math.max(V1,V2); 
            set.add(V1);
            set.add(V2);                       
        }
        //离散化(给人员重新编号)
        ArrayList<Integer> list=new ArrayList<>(set);
        Collections.sort(list);
        for(int i=0;i<list.size();i++){
            map.put(list.get(i),i+1);
        }
        for(int i=0;i<=list.size();i++){
            father[i]=i;
        }
        //将初始时的边转换为一个long类型数据并放入哈希表中
        for(int i=0;i<m;i++){
            int a=map.get(edge[i][0]),b=map.get(edge[i][1]);
            Long hashcode=tohashcode(a,b);
            stringmap.put(hashcode,i);
        }         
        //离线后还需要真正运行的操作索引集合
        ArrayList<Integer> edge_add=new ArrayList<>();
        //重前往后遍历时在处理同一条边删除两次时只处理第一次
        for(int i=0;i<q;i++){
            if(question[i][0]==1){
                int a=map.get(question[i][1]),b=map.get(question[i][2]);
                long hashcode=tohashcode(a,b);
                //判断该边是否存在且没有被处理掉
                if(stringmap.get(hashcode)!=null){
                    edge_add.add(i);
                    stringmap.remove(hashcode);
                }
            }else{
                edge_add.add(i);
            }
        }
        //现在stringmap中只存在不会被删除的边获取出来
        for(Map.Entry<Long,Integer> entry:stringmap.entrySet()){
            int index=entry.getValue();
            int a=map.get(edge[index][0]),b=map.get(edge[index][1]);
            union(a,b);
        }
        ArrayList<String> ans=new ArrayList<>();
        //从下往上遍历
        for(int i=edge_add.size()-1;i>=0;i--){
            int index=edge_add.get(i);
            int a=map.get(question[index][1]),b=map.get(question[index][2]);
            if(question[index][0]==2){
                String R=find(a)==find(b)?"Yes":"No";
                ans.add(R);
            }else{
                union(a,b);
            }
        }
        for(int i=ans.size()-1;i>=0;i--){
            bw.write(ans.get(i)+"\n");
        }
        bw.close();
        br.close();
    }
}

发表于 2025-02-17 23:03:02 回复(0)
一开始没做出来,在前面Java兄弟代码上改了一下,过了。
思路是反向并查集
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        // 注意 hasNext 和 hasNextLine 的区别
        while (in.hasNextInt()) { // 注意 while 处理多个 case
            int n = in.nextInt();
            int m = in.nextInt();
            int q = in.nextInt();
            //初始化HashSet容量以避免扩容
            Set<Long> edgesSet = new HashSet<>(m);
            while (m-- > 0) {
                long node1 = in.nextInt();
                long node2 = in.nextInt();
                long edge = node1 < node2 ? (node1 << 32) + node2 : (node2 << 32) + node1;
                edgesSet.add(edge);
            }
           
            int[][] ops = new int[q][3];
            while (q-- > 0) {
                ops[q][0] = in.nextInt();
                ops[q][1] = in.nextInt();
                ops[q][2] = in.nextInt();
                if (ops[q][0] == 1) {
                    long edgeNum = ops[q][1] < ops[q][2] ? ((long)ops[q][1] << 32) + ops[q][2] : ((
                                       long)ops[q][2] << 32) + ops[q][1];
                    if (edgesSet.contains(edgeNum)) {
                        edgesSet.remove(edgeNum);
                    } else {
                        ops[q][0] = 3;
                    }
                }
            }
            //这里是Math.min(n, 100000)的原因是:有一个案例会阴你一手,给n=10^9,初始化并查集里的HashMap时会OOM
            UnionFind2 uf = new UnionFind2(Math.min(n, 100000));
            for (long edge : edgesSet) {
                int a = (int) (edge >> 32);
                int b = (int) (edge & Integer.MAX_VALUE);
                uf.union(a, b );
            }
            boolean[] stack = new boolean[ops.length];
            int top = 0;
            for (int[] op : ops) {
                if (op[0] == 1) {
                    uf.union(op[1], op[2] );
                } else if (op[0] == 2) {
                    stack[top++] = uf.find(op[1] ) == uf.find(op[2]);
                }
            }
            //用StringBuilder一次性输出答案,以避免多次调用System.out.print()
            StringBuilder sb = new StringBuilder();
            while (top > 0) {
                sb.append(stack[--top] ? "Yes\n" : "No\n");
            }
            System.out.print(sb.toString());
        }
    }

    static class UnionFind2 {
        Map<Integer, Integer> parents;

        public UnionFind2(int n) {
            //初始化HashMap容量以避免扩容
            parents = new HashMap<>(n);
        }

        public int find(int x) {
            int xParent = parents.getOrDefault(x, -1);
            if (xParent < 0) {
                return x;
            }
            parents.put(x, find(xParent));
            return parents.get(x);
        }

        public void union(int x, int y) {
            int rootX = find(x), rootY = find(y);
            if (rootX == rootY) return;
            int xRank = parents.getOrDefault(rootX, -1);
            int yRank = parents.getOrDefault(rootY, -1);
            if (xRank < yRank) {
                parents.put(rootX, xRank + yRank);
                parents.put(rootY, rootX);
            } else {
                parents.put(rootY, xRank + yRank);
                parents.put(rootX, rootY);
            }
        }
    }
}

编辑于 2024-06-08 01:11:20 回复(3)
这题java真的有办法ac吗???
import java.util.*;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        // 注意 hasNext 和 hasNextLine 的区别
        while (in.hasNextInt()) { // 注意 while 处理多个 case
            int n = in.nextInt();
            int m = in.nextInt();
            int q = in.nextInt();

            long MOD = 1000000001;
            Set<Long> edgesSet = new HashSet<>();
            while (m-- > 0) {
                int node1 = in.nextInt();
                int node2 = in.nextInt();
                edgesSet.add(node1 * MOD + node2);
            }
            int[][] ops = new int[q][3];
            while (q-- > 0) {
                ops[q][0] = in.nextInt();
                ops[q][1] = in.nextInt();
                ops[q][2] = in.nextInt();
                if (ops[q][0] == 1) {
                    long edgeNum1 = ops[q][1] * MOD + ops[q][2];
                    long edgeNum2 = ops[q][2] * MOD + ops[q][1];
                    if (edgesSet.contains(edgeNum1)) {
                        edgesSet.remove(edgeNum1);
                    } else if (edgesSet.contains(edgeNum2)) {
                        edgesSet.remove(edgeNum2);
                    } else {
                        ops[q][0] = 3;
                    }
                }
            }

            UnionFind uf = new UnionFind(n);
            for (long edge: edgesSet) {
                int a = (int) (edge / MOD);
                int b = (int) (edge % MOD);
                uf.union(a - 1, b - 1);
            }
            List<Boolean> list = new ArrayList<>();
            for (int[] op: ops) {
                if (op[0] == 1) {
                    uf.union(op[1] - 1, op[2] - 1);
                } else if (op[0] == 2) {
                    list.add(uf.find(op[1] - 1) == uf.find(op[2] - 1));
                }
            }
            for (int i = list.size() - 1; i >= 0; i--) {
                String output = list.get(i)? "Yes": "No";
                System.out.println(output);
            }
        }
    }

    static class UnionFind {
        Map<Integer, Integer> repre;
        Map<Integer, Integer> rank;

        public UnionFind(int n) {
            repre = new HashMap<>();
            rank = new HashMap<>();
        }

        public int find(int x) {
            int xParent = repre.getOrDefault(x, x);
            if (xParent != x) {
                repre.put(x, find(xParent));
            }
            return repre.getOrDefault(x, x);
        }

        public void union(int x, int y) {
            int rootX = find(x), rootY = find(y);
            if (rootX == rootY) return;
            int xRank = rank.getOrDefault(rootX, 0);
            int yRank = rank.getOrDefault(rootY, 0);
            if (xRank < yRank) {
                repre.put(rootX, rootY);
            } else if (xRank > yRank) {
                repre.put(rootY, rootX);
            } else {
                repre.put(rootX, rootY);
                rank.put(rootY, yRank + 1);
            }
        }
    }
}


不用Map用数组,会在1e9的地方炸内存;换成map,会告诉你用时2001ms超时。
我盯了半天也想不到一丁点可以优化的地方了……
发表于 2024-03-17 23:14:30 回复(4)