首页 > 试题广场 >

小美的朋友关系

[编程题]小美的朋友关系
  • 热度指数:3967 时间限制: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 号互相认识了。

判断点连通第一时间并查集,因为过程是删边并查集很难处理,所以考虑从后往前遍历查询来不断加边。

笔试的时候debug半天没A出来,最后一想果然是重边的问题,以及点的编号1e9也是比较烦人的点。

#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
using ll = long long;
ll n,m,q;
unordered_mapfa;
map,int>mp;
int Find(int a){
    if(a == fa[a]){
        return a;
    }
    return fa[a] = Find(fa[a]);
}
void unite(int a,int b){
    a = Find(a);
    b = Find(b);
    fa[a] = b;
}
void solve(){
    for(int i = 1,u,v;i <= m;++i){
        cin >> u >> v;
        fa[u] = u;
        fa[v] = v;
        mp[{u,v}]++;
        mp[{v,u}]++;
    }
    stack>s;
    for(int i = 1,op,u,v;i <= q;++i){
        cin >> op >> u >> v;
        s.push(make_tuple(op,u,v));
        if(op == 1){
            mp[{u,v}]--;
            mp[{v,u}]--;
        }
    }
    // std::get(s.top());
    for(auto& edge : mp){
        if(edge.second > 0){
            unite(edge.first.first,edge.first.second);
        }
    }
    stackans;
    while(!s.empty()){
        auto p = s.top();
        s.pop();
        if(std::get(p) == 1){
            mp[{get(p),get(p)}]++;
            mp[{get(p),get(p)}]++;
            if(mp[{get(p),get(p)}] > 0)
                unite(std::get(p),std::get(p));
        }
        else{
            if(fa[std::get(p)] == 0 || fa[std::get(p)] == 0){
                ans.push("No");
                continue;
            }
            ans.push(Find(std::get(p)) == Find(std::get(p)) ? "Yes" : "No");
        }
    }
    while(!ans.empty()){
        cout << ans.top() << "\n";
        ans.pop();
    }
}
int main() {
    int a, b;
    while (cin >> n >> m >> q) {
        solve();
    }
}
发表于 2024-03-12 18:30:06 回复(5)
这题确实恶心,首先需要离散化,然后离线时间回溯并查集,还有许多小细节需要处理。
#include <iostream>
#include <bits/stdc++.h>
using namespace std;

unordered_map<int,int>fa;
map<pair<int,int>,bool>mp;
vector<vector<int>>b;

int find(int x){
    if(x==fa[x])return x;
    else return fa[x] = find(fa[x]);
}

void combine(int x, int y){
    int fx = find(x), fy = find(y);
    fa[fx] = fy;
}

int main() {
    int n,m,q;
    cin>>n>>m>>q;
    b.resize(q+1);
    for(int i=0; i<m; i++){
        int x,y;
        cin>>x>>y;
        fa[x] = x, fa[y] = y;
        if(x>y)swap(x,y);
        mp.insert({{x,y},true});
    }
    for(int i=1; i<=q; i++){
        b[i].resize(3);
        cin>>b[i][0]>>b[i][1]>>b[i][2];
        if(b[i][1]>b[i][2])swap(b[i][1],b[i][2]);
        if(b[i][0]==1 && mp.count({b[i][1],b[i][2]}))mp[{b[i][1],b[i][2]}] = false;
    }
    for(auto &it:mp){
        auto [x,y] = it.first;
        if(it.second)combine(x, y);
    }
    stack<string>ans;
    for(int i=q; i>=1; i--){
        int fx = find(b[i][1]), fy = find(b[i][2]);
        if(b[i][0]==2){
            if(fx && fx==fy)ans.push("Yes");
            else ans.push("No");
        }else{
            if(mp.count({b[i][1],b[i][2]})) fa[fx] = fy;
        }
    }
    while(ans.size()){
        cout<<ans.top()<<endl;
        ans.pop();
    }
    return 0;
}


发表于 2024-03-14 22:59:14 回复(2)

反向并查集:

由于并查集仅支持插入关系而不能删除已有的关系,因此要进行“删除”的话得反向思考。先遍历所有关系和事件,提取出所有事件结果后仍保持的关系,将它们加入并查集中。然后逆序遍历事件,正序时遇到的“删除”事件相当于逆序下的“添加”,因此碰到删除时进行添加操作即可。
参考代码如下:

#include <iostream>
#include <map>
#include <set>
#include <stack>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;

struct UF
{
    // 题给范围n太大, 用map存储
    map<int, int> pa;

    int find(int a)
    {
        if (pa[a] != a)
            pa[a] = find(pa[a]);
        return pa[a];
    }

    bool isConnect(int a, int b)
    {
        return find(a) == find(b);
    }

    void add(int x, int y)
    {
        pa[find(x)] = find(y);
    }
};

int main() {
    int n, m, q;
    cin >> n >> m >> q;

    // 存储关系
    UF uf;
    set<pair<int, int>> relations;
    while (m --)
    {
        int a, b;
        cin >> a >> b;

        // 初始化
        uf.pa[a] = a;
        uf.pa[b] = b;
        // 使用set和强制规定a < b, 避免重边
        if (a > b)
            swap(a, b);
        relations.insert({a, b});
    }

    // 存储事件并维护关系
    vector<vector<int>> acts;
    while (q --)
    {
        int op, a, b;
        cin >> op >> a >> b;

        // 初始化
        uf.pa[a] = a;
        uf.pa[b] = b;

        if (a > b)
            swap(a, b);      
        // 删除操作, 合法就删除, 不合法就跳过
        if (op == 1)
        {
            if (relations.find({a, b}) != relations.end())
                relations.erase({a, b});
            else
                continue;
        }

        vector<int> tmp = {op, a, b};
        acts.emplace_back(tmp);
    }

    // 用剩余的关系建立并查集
    for (auto& [a, b] : relations)
        uf.add(a, b);

    // 逆向遍历事件
    reverse(acts.begin(), acts.end());
    stack<string> ans;
    for (auto& act : acts)
    {
        int op = act[0], a = act[1], b = act[2];
        if (op == 1)
            uf.add(a, b);
        else
        {
            if (uf.isConnect(a, b))
                ans.push("Yes");
            else
                ans.push("No");
        }
    }

    // 输出答案
    while (!ans.empty())
    {
        cout << ans.top() << '\n';
        ans.pop();
    }
}

参考资料

美团笔试题题解

发表于 2024-05-14 22:42:13 回复(1)
这道题是这里最难的一道思路是反向并查集和离散化以及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 AC代码:(快读快输模板)996ms
import java.util.*;
import java.io.*;
import java.math.*;

public class Main {
	static BufferedReader bf = new BufferedReader(new InputStreamReader(System.in),65535);
	static StreamTokenizer st = new StreamTokenizer(bf);
	static PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
	static int n,m,maxn=200005,inf = 0x3f3f3f3f;
	static long ans=0,mod=(int)1e9+7,INF=(long)2e18;
	public static void main(String[]args) throws IOException{
		new Task().main();
		pw.flush();
	}
	
	static int I() throws IOException{
		st.nextToken();
		return (int)st.nval;
	}

	static class Task{
		void main()throws IOException{
			int t=1;
			//t=sc.nextI();
			while(t-->0) solve();
		}
		
		int []p = new int [maxn];
		Map<Integer,Integer> id = new HashMap<>();
		Set<Long> edge = new HashSet<>();
		Set<Long> del = new HashSet<>();
		Vector<int[]> ask = new Vector<>();
		
		void add(int u,int v) {
			int uu=find(u),vv=find(v);
			if(uu!=vv) p[uu]=vv;
		}
		
		private int find(int v) {
			// TODO Auto-generated method stub
			if(p[v]==v) return v;
			return p[v]=find(p[v]);
		}

		private void solve() throws IOException {
			n=sc.nextI();m=sc.nextI();int q=sc.nextI();
			int tot=0;
			for(int i=1;i<maxn;i++) p[i]=i;
			for(int i=1;i<=m;i++) {
				int u=sc.nextI(),v=sc.nextI();
				if(!id.containsKey(u)) id.put(u, ++tot);
				if(!id.containsKey(v)) id.put(v, ++tot);
				u=id.get(u);v=id.get(v);
				if(u>v) {
					int t=u;u=v;v=t;
				}
				edge.add(1L*u*maxn+v);
			}
			for(int i=1;i<=q;i++) {
				int op=sc.nextI(),u=sc.nextI(),v=sc.nextI();
				if(!id.containsKey(u)) id.put(u, ++tot);
				if(!id.containsKey(v)) id.put(v, ++tot);
				u=id.get(u);v=id.get(v);
				if(u>v) {
					int t=u;u=v;v=t;
				}
				ask.add(new int [] {op,u,v});
				if(op==1) del.add(1L*u*maxn+v);
			}
			for(long x:edge) {
				if(del.contains(x)) continue;
				int u = (int)(x/maxn),v=(int)(x%maxn);
				add(u,v);
			}
			Vector<Integer> ans = new Vector<>();
			for(int i=q-1;i>=0;i--) {
				int[] qu = ask.get(i);
				int op = qu[0],u=qu[1],v=qu[2];
				if(op==2) {
					if(find(u)!=find(v)) ans.add(0);
					else ans.add(1);
				}
				else {
					if(edge.contains(1L*u*maxn+v))add(u,v);
				}
			}
			for(int i=ans.size()-1;i>=0;i--) {
				pw.println(ans.get(i)==1?"Yes":"No");
			}
		}
	}
	
	static class Input {
		Input(InputStream in) { this.in = in; } InputStream in;
		byte[] bb = new byte[1 << 15]; int i, n;
		byte getc() {
			if (i == n) {
				i = n = 0;
				try { n = in.read(bb); } catch (IOException e) {}
			}
			return i < n ? bb[i++] : 0;
		}
		int nextI() {
			byte c = 0; while (c <= ' ') c = getc();
			int f=1;
			int a = 0; while (c > ' ') { if(c=='-') {f=-1;c = getc();continue;}a = a * 10 + c - '0'; c = getc(); }
			return a*f;
		}
		long nextL() {
			byte c = 0; while (c <= ' ') c = getc();
			int f=1;
			long a = 0; while (c > ' ') { if(c=='-') {f=-1;c = getc();continue;}a = a * 10 + c - '0'; c = getc(); }
			return a*f;
		}
		byte[] cc = new byte[1 << 7];
		String next() {
			byte c = 0; while (c <= ' ') c = getc();
			int k = 0;
			while (c > ' ') {
				if (k == cc.length) cc = Arrays.copyOf(cc, cc.length << 1);
				cc[k++] = c; c = getc();
			}
			return new String(cc, 0, k);
		}
	}
	static Input sc = new Input(System.in);
	static String S() throws IOException {
		String res=bf.readLine();
		while(res.equals("")) res = bf.readLine();
		return res;
	}
}


发表于 2024-06-17 13:57:57 回复(1)
几个注意点:1.使用map避免离散化 2.反向合并淡忘的朋友时,要注意只有初始时为朋友的u, v才能合并
#include <bits/stdc++.h>
using namespace std;
using PII = pair<int, int>;
unordered_map<int, int> p;

int find(int x){
    if(p[x] != x) p[x] = find(p[x]);
    return p[x];
}
void merge(int u, int v){
    p[find(u)] = find(v);
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, m, q;
    cin >> n >> m >> q;
    set<PII> friends;
    for(int i = 0; i < m; ++i){
        int u, v;
        cin >> u >> v;
        if(u > v) swap(u, v);
        p[u] = u, p[v] = v;
        friends.insert({u, v});
    }
    vector<tuple<int, int, int>> que;
    set<tuple<int, int, int>> del;
    for(int i = 0; i < q; ++i){
        int op, u, v;
        cin >> op >> u >> v;
        if(u > v) swap(u, v);
        p[u] = u, p[v] = v;
        que.push_back({op, u, v});
        del.insert({op, u, v});
    }
    for(auto [u, v]: friends){
        tuple<int, int, int> x = {1, u, v};
        if(del.count(x)) continue;
        merge(u, v);
    }
    vector<int> ans;
    for(int i = q - 1; i >= 0; --i){
        auto [op, u, v] = que[i];
        if(op == 1 && friends.count({u, v})){
            merge(u, v);
        }else if(op == 2){
            u = find(u), v = find(v);
            ans.push_back(u == v);
        }
    }
    reverse(ans.begin(), ans.end());
    for(int ok: ans) cout << (ok ? "Yes" : "No") << '\n';
    return 0;
}



编辑于 2024-04-05 16:25:04 回复(2)
这题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)

真的坑,写了好几遍并查集,都以为字节写错了,最后才发现本就没有关系的两个人不需要再插入并查集了,怪不得一直超时和爆栈。

use std::collections::{HashMap, HashSet};
use std::io::{self, BufRead};

struct Tree {
    parent: HashMap<usize, usize>,
}

impl Tree {
    fn new() -> Tree {
        Tree {
            parent: HashMap::new(),
        }
    }

    fn union(&mut self, x: usize, y: usize) {
        let x = self.find(x);
        let y = self.find(y);
        if x == y {
            return ;
        }
        self.parent.insert(y, x);
    }

    fn find(&mut self, x: usize) -> usize {
        let p = *self.parent.get(&x).unwrap();
        if p != x {
            let f = self.find(p);
            self.parent.insert(x, f);
        }
        *self.parent.get(&x).unwrap()
    }
}

fn main() {
    let mut stdin = io::stdin();
    let mut line = String::new();
    stdin.read_line(&mut line).unwrap();

    let mut iter = line.trim().split_whitespace();
    let n: usize = iter.next().unwrap().parse().unwrap();
    let m: usize = iter.next().unwrap().parse().unwrap();
    let q: usize = iter.next().unwrap().parse().unwrap();

    let mut realtions = HashSet::new();
    let mut S = Tree::new();

    for i in 0..m {
        line.clear();
        stdin.read_line(&mut line).unwrap();

        iter = line.trim().split_whitespace();
        let x: usize = iter.next().unwrap().parse().unwrap();
        let y: usize = iter.next().unwrap().parse().unwrap();
        S.parent.entry(x).or_insert(x);
        S.parent.entry(y).or_insert(y);
        realtions.insert((x, y));
    }

    let mut ops = Vec::new();
    for i in 0..q {
        line.clear();
        stdin.read_line(&mut line).unwrap();

        iter = line.trim().split_whitespace();
        let op: usize = iter.next().unwrap().parse().unwrap();
        let x: usize = iter.next().unwrap().parse().unwrap();
        let y: usize = iter.next().unwrap().parse().unwrap();

        if op == 1 {
            if realtions.remove(&(x, y)) || realtions.remove(&(y, x)) {
                ops.push((op, x, y));
            } 
        } else {
            ops.push((op, x, y));
        }
    }

    for (x, y) in realtions {
        S.union(x, y);
    }

    let mut res = Vec::new();
    for i in (0..ops.len()).rev() {
        match ops[i].0 {
            1 => {
                S.union(ops[i].1,ops[i].2);
            },
            2 => {
                if !S.parent.contains_key(&ops[i].1) {
                    S.parent.insert(ops[i].1, ops[i].1);
                }
                if !S.parent.contains_key(&ops[i].2) {
                    S.parent.insert(ops[i].2, ops[i].2);
                }

                let mut flag = S.find(ops[i].1) == S.find(ops[i].2);
                res.push(if flag { "Yes" } else { "No" });
            },
            _ => {

            }
        }
    }

    res.reverse();

    for r in res {
        println!("{}", r);
    }

}
发表于 2024-08-22 19:36:49 回复(0)
class UnionFind:
    def __init__(self):
        self.father = {}

    def find(self, u):
        if u == self.father[u]:
            return u
        self.father[u] = self.find(self.father[u])
        return self.father[u]

    def join(self, u, v):
        u = self.find(u)
        v = self.find(v)
        if u != v:
            self.father[v] = u
n, m, q = list(map(int, input().strip().split()))

uf = UnionFind()
relationship = set()
events = []



# 读取初始朋友关系
for _ in range(m):
    u, v = list(map(int, input().strip().split()))
    relationship.add((u, v))
    uf.father.setdefault(u, u)
    uf.father.setdefault(v, v)


# 读取事件
for _ in range(q):
    op, u, v = map(int, input().strip().split())
    if op == 1:
        if (u, v) in relationship:
            relationship.remove((u, v))
        elif (v, u) in relationship:
            relationship.remove((v, u))
        else:
            continue
    events.append([op, u, v])


# 将不会被淡忘的关系加入并查集
for u, v in relationship:
    uf.join(u, v)


# 逆序处理事件
result = []
for op, u, v in events[::-1]:
    if op == 1:
        uf.join(u, v)
    else:
        uf.father.setdefault(u, u)
        uf.father.setdefault(v, v)
        if uf.find(u) == uf.find(v):
            result.append('Yes')
        else:
            result.append('No')

for res in result[::-1]:
    print(res)


发表于 2024-06-22 11:50:25 回复(0)
#include <bits/stdc++.h>
using namespace std;

const int N = 1e5+10;
unordered_set<string> start_edge, res_edge;
unordered_map<string, int> first_delet; // 维护第一次删除边的位置
int n, q, m;
unordered_map<int,int> p;
stack<string> query; 

struct Op {
    int op;
    int u;
    int v;
}ops[N];

int find(int x)
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

pair<int,int> get_edges(string s) {
    pair<int,int> res;
    for (int i = 0; i < s.size(); i ++) {
        if (s[i] == ',') {
            res.first = stoi(s.substr(0,i));
            res.second = stoi(s.substr(i+1));
            break;
        }
    }
    return res;
}
int main() {
    cin >> n >> m >> q;
    while (m --) {
        int u, v;
        cin >> u >> v;
        if (u > v) {
            int tmp = u;
            u = v;
            v = tmp;
        }
        p[u] = u;
        p[v] = v;
        string entry = to_string(u) + "," + to_string(v);
        start_edge.insert(entry);
        res_edge.insert(entry);
    }
    for (int i = 1; i <= q; i ++) {
        int op, u , v;
        cin >> op >> u >> v;
        if (u > v) {
            int tmp = u;
            u = v;
            v = tmp;
        }
        ops[i] = {op, u, v};
        if (op == 1) {
            // 淡忘
            string entry = to_string(u) + "," + to_string(v);
            if (res_edge.find(entry) != res_edge.end())
            {
                res_edge.erase(entry); // 删掉这条边
                first_delet[entry] = i; // 也是第一次删除的
            }
        }
    }
    // 构造最终的并查集
    // for (int i = 1; i <= n; i ++) p[i] = i;
    
    for (auto & node : res_edge) {
        // 解析这个字符串得到两条边
        auto edges = get_edges(node);
        int u = edges.first, v = edges.second;
        int a = find(u), b = find(v);
        if (a != b) {
            p[a] = b;
        }
    }
    
    // 从后往前查询,结果保留在栈中
    for (int i = q; i >= 1; i --) {
        int op = ops[i].op, u = ops[i].u, v = ops[i].v;
        if (u > v) {
            int tmp = u;
            u = v;
            v = tmp;
        }
        if (op == 2) {
            // 查询
            int a = find(u), b = find(v);
            if (a != b || a == 0) query.push("No");
            else query.push("Yes");
        } else {
            // 先看有没有这条边
            string entry = to_string(u) + "," + to_string(v);
            if (first_delet.find(entry) != first_delet.end() && first_delet[entry] == i) {
                // 删除过这条边,而且是在这个位置被删除的, 要把这条边加进去
                int a = find(u), b = find(v);
                if (a != b) {
                    p[a] = b;
                }
            } 
        }
    }
    while (!query.empty()) {
        cout << query.top() << "\n";
        query.pop();
    }
    return 0;
    
}


发表于 2024-05-14 13:00:32 回复(0)
go
package main

import (
	"bufio"
	"fmt"
	"os"
)

type oper struct {
	op, a, b int
}

type bian struct {
	a, b int
}

func main() {
	var n, m, q int
	fmt.Scan(&n, &m, &q)
	pre := make(map[int]int)
	
	friend := make(map[int64]*bian, m)
	del := make(map[int64]bool, q)
	ops := make([]*oper, 0, q)
	in := bufio.NewReader(os.Stdin)
	for i := 0; i < m; i++ {
		var u, v int
		fmt.Fscan(in, &u, &v)
        pre[u] = u
		pre[v] = v
		friend[makeKey(u, v)] = &bian{u, v}
	}

	for i := 0; i < q; i++ {
		var op, a, b int
		fmt.Fscan(in, &op, &a, &b)
		if op == 1 {
			del[makeKey(a, b)] = true
		}
		ops = append(ops, &oper{op, a, b})
	}

	for _, b := range friend {
		ba, bb := b.a, b.b
		if !del[makeKey(ba, bb)] {
			join(pre, ba, bb)
		}
	}

	ans := make([]bool, 0, q)
	for i := len(ops) - 1; i >= 0; i-- {
		op := ops[i]
		if op.op == 1 && friend[makeKey(op.a, op.b)] != nil {
			join(pre, op.a, op.b)
		} else if op.op == 2 {
			if find(pre, op.a) == find(pre, op.b) {
				ans = append(ans, true)
			} else {
				ans = append(ans, false)
			}
		}
	}

	for i := len(ans) - 1; i >= 0; i-- {
		if ans[i] {
			fmt.Println("Yes")
		} else {
			fmt.Println("No")
		}
	}

}

func find(p map[int]int, a int) int {
    // 可能有不在初始化map的节点进行操作
    //===============
    if p[a] == 0{
        p[a] = a
    }
    //===============
	if p[a] != a {
		p[a] = find(p, p[a])
	}
	return p[a]
}

func join(p map[int]int, a, b int) {
	pa, pb := find(p, a), find(p, b)
	if pa != pb {
		p[pb] = pa
	}
}

func makeKey(a, b int) int64 {
	if a > b {
		return int64(a)<<32 | int64(b)
	}
	return int64(b)<<32 | int64(a)
}


发表于 2024-05-06 17:34:32 回复(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)
tkw头像 tkw
离散化 + 离线处理,注意重边

#include "bits/stdc++.h"

const int N = 3e5 + 10;
const int M = 1010;
typedef long long ll;
using namespace std;
#define TEST int t; cin >> t; while(t--) solve();
#define endl '\n'
#define yes cout << "YES" << endl
#define no cout << "NO" << endl
typedef pair<int, int> pii;
typedef pair<ll, int> pli;
typedef pair<ll, ll> pll;
typedef pair<int, ll> pil;
int INF = 0x3f3f3f3f;
const int mod = 1e9 + 7;

int n, a[N], q, m;
int p[N], f[N];


int find(int x) {
    return p[x] == x ? p[x] : p[x] = find(p[x]);
}

int select(vector<int>& v, int k) {
    int l = 0, r = v.size() - 1;
    while (l < r) {
        int mid = l + r >> 1;
        if (v[mid] < k) l = mid + 1;
        else r = mid;
    }
    if (v[l] != k) return -1;
    return l;
}


void solve() {
    cin >> n >> m >> q;
    int u, v, op;
    vector<pii> tmp;
    vector<int> alls;
    map<pii, int> mp;
    while (m--) {
        cin >> u >> v;
        int st = min(u, v), ed = max(u, v);
        alls.emplace_back(st);
        alls.emplace_back(ed);
        tmp.emplace_back(st, ed);
        mp[pii(st, ed)]++;
    }
    sort(alls.begin(), alls.end());
    alls.erase(unique(alls.begin(), alls.end()), alls.end());
    for (int i = 0; i < N; i++) p[i] = i;
    
    vector<vector<int>> ops;
    for (int i = 0; i < q; i++) {
        cin >> op >> u >> v;
        int st = min(u, v), ed = max(u, v);
        ops.emplace_back(vector<int>{op, st, ed});
        if (op == 1) {
            if (mp.find(pii(st, ed)) != mp.end()) {
                if (mp[pii(st, ed)] <= 0) continue;
                mp[pii(st, ed)]--;
                f[i] = 1;
            }
        }
    }
    
    for (auto& [k, v] : mp) {
        if (v <= 0) continue;
        u = k.first; v = k.second;
        int p1 = find(select(alls, u)), p2 = find(select(alls, v));
        p[p1] = p2;
    }
    
    stack<string> ans;
    for (int i = q - 1; i >= 0; i--) {
        u = ops[i][1]; v = ops[i][2];
        int st = min(u, v), ed = max(u, v);
        if (ops[i][0] == 1 && f[i]) {
            int p1 = find(select(alls, st)), p2 = find(select(alls, ed));
            p[p1] = p2;
        }
        if (ops[i][0] == 2) {
            int idx1 = select(alls, st), idx2 = select(alls, ed);
            if (idx1 == -1 || idx2 == -1) ans.push("No");
            else {
                int p1 = find(idx1), p2 = find(idx2);
                if (p1 == p2) ans.push("Yes");
                else ans.push("No");
            }
        }
    }
    while (ans.size()) {
        cout << ans.top() << endl;
        ans.pop();
    }
}


int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    //TEST
    solve();
    return 0;
}



发表于 2024-03-22 17:32:53 回复(0)
#include <cstdlib>
#include <map>
#include <vector>
#include <iostream>
#include <memory>
#include <utility>
#include <ctime>
#include <algorithm>
using namespace std;

class DSU
{
    struct ANode
    {
        int id;
        ANode *fa;
        ANode() = default;
        ANode(int _):id(_),fa(nullptr){}
    };

    map<int,std::unique_ptr<ANode> > RecordMap;

    ANode* GetNode(int id)
    {
        if(RecordMap.find(id) == RecordMap.end())
        {
            RecordMap.insert(make_pair(id, std::make_unique<ANode>(id)));
        }
        return RecordMap[id].get();
    }

    int GetFa(int id)
    {
        ANode* node = GetNode(id), *ret =nullptr, *tmp;
        ret = node;
        while(ret->fa)
        {
            ret = ret->fa;
        }
        while(node!=ret)
        {
            tmp = node->fa;
            node->fa = ret;
            node = tmp;
        }
        return ret->id;
    }

public:
    DSU()
    {
        RecordMap.clear();
    }

    void init()
    {
        RecordMap.clear();
    }

    void Link(int x,int y)
    {
        x = GetFa(x);
        y = GetFa(y);

        if(x==y)
        {
            // Already Linked
            return;
        }

        if(rand()&1)
        {
            swap(x,y);
        }
        ANode *X = GetNode(x), *Y = GetNode(y);
        Y->fa = X;
    }

    bool IsLinked(int x,int y)
    {
        x = GetFa(x);
        y = GetFa(y);
        return x==y;
    }
};

struct Instruction
{
    int opr,u,v;
    Instruction() = default;
    Instruction(int _o,int _u, int _v):
        opr(_o),u(_u),v(_v){}
};
vector<Instruction> InstructionPool;
array<pair<int,int>,100005> Relations;
vector<bool> AnsStack;

DSU UnionFindSet;

int main() {
    int n,m,q;
    int op,a,b;
    while (cin >> n >> m >> q) { // 注意 while 处理多个 case
        UnionFindSet.init();
        InstructionPool.clear();
        AnsStack.clear();
        Relations.fill(make_pair(0,0));
        for(int i=0;i<m;++i)
        {
            cin >> Relations[i].first >> Relations[i].second;
            if(Relations[i].first > Relations[i].second)
            {
                swap(Relations[i].first,Relations[i].second);
            }
        }
        sort(Relations.begin(),Relations.begin()+m);

        InstructionPool.reserve(q+m);
        for(int i=0;i<q;++i)
        {
            cin >> op >> a >> b;
            if(a>b)
            {
                swap(a,b);
            }

            if(op==1)
            {
                int lb = lower_bound(Relations.begin(), Relations.begin()+m, make_pair(a,b)) - Relations.begin();
                if(Relations.at(lb) != make_pair(a, b)) continue;
                InstructionPool.emplace_back(op,a,b);
                Relations[lb] = make_pair(0,0);
            }
            else
            {
                InstructionPool.emplace_back(op,a,b);
            }
        }

        for(int i=0;i<m;++i)
        {
            if(Relations[i].first && Relations[i].second)
            {
                InstructionPool.emplace_back(1,Relations[i].first,Relations[i].second);
            }
        }

        for(auto it=InstructionPool.rbegin();it!=InstructionPool.rend();++it)
        {
            //printf("%d %d %d\n",it->opr,it->u,it->v);
            if(it->opr == 1)
            {
                UnionFindSet.Link(it->u, it->v);
            }
            else
            {
                AnsStack.emplace_back( UnionFindSet.IsLinked(it->u, it->v) );
            }
        }

        for(auto it=AnsStack.rbegin();it!=AnsStack.rend();++it)
        {
            puts(*it?"Yes":"No");
        }
    }
}
// 64 位输出请用 printf("%lld")
用Map规避离散化。
操作顺序上倒序来看问题可以用并查集来解决。
所以最终答案是并查集+离线处理。
注意题目中存在一些无效删除,需要特殊判定处理。
已经很久没写算法代码了写的挫了大家轻喷
发表于 2025-05-09 23:29:15 回复(0)
#include <bitset>
#include <iostream>
#include <vector>
#include <array>
#include <unordered_map>
#include <set>
using namespace std;
 
int findroot(unordered_map<int, int>& mergeset, int n) {
    while (mergeset.count(n) > 0 && mergeset[n] != n) {
        n = mergeset[n];
    }
    return n;
}
 
void mergesetcombine(unordered_map<int, int>& mergeset, int u, int v) {
    int uf = findroot(mergeset, u);
    int vf = findroot(mergeset, v);
    mergeset[uf] = mergeset[vf] = mergeset[u] = mergeset[v] = min(uf, vf);
}
 
int main() {
    int n, m, q; // 总人数, 关系数量,事件数量
    cin >> n >> m >> q;
    set<pair<int, int>> relationship;
    int u, v;
    for (int i = 0; i < m; i++) {
        cin >> u >> v;
        if (u > v) swap(u, v);
        relationship.emplace(--u, --v);
    }
    vector<bool> success(q, false);
    vector<array<int, 3>> ops;
    int op;
    for (int i = 0; i < q; i ++) {
        cin >> op >> u >> v;
        if (u > v) swap(u, v);
        ops.emplace_back(array<int, 3> {op, --u, --v});
        if (op == 2 || u == v) continue;
        pair<int, int> key{u, v};
        if (relationship.count(key) > 0) {
            success[i] = true;
            relationship.erase(key);
        }
    }
    // 并查集
    unordered_map<int, int> mergeset;
 
    // 初始化并查集
    for (const auto& item : relationship) {
        mergesetcombine(mergeset, item.first, item.second);
    }
 
    // 逆向操作
    vector<bool> result;
    for (int i = q - 1; i >= 0; i--) {
        array<int, 3> op = ops[i];
        if (op[0] == 1) {
            if (success[i]) mergesetcombine(mergeset, op[1], op[2]);
        } else if (findroot(mergeset, op[1]) == findroot(mergeset, op[2])) {
            result.emplace_back(true);
        } else {
            result.emplace_back(false);
        }
    }
 
    for (int i = result.size() - 1; i >= 0; i--) {
        if (result[i]) cout << "Yes" << endl;
        else cout << "No" << endl;
    }
}

发表于 2025-04-01 20:10:14 回复(0)
java解法,采用快速输入输出,并查集和逆向遍历事件,当前时间是2025/3/24,亲测可过(若出现超时可以多试几遍)
import java.util.*;
import java.io.*;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) throws Exception {
        //快速输入
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        
        StringBuilder build = new StringBuilder();
        String line = null;
        String[] s = in.readLine().split(" ");
        int n = Integer.valueOf(s[0]);
        int m = Integer.valueOf(s[1]);
        int q = Integer.valueOf(s[2]);
        //采用map来构建并查集,避免爆内存
        Map<Integer, Integer> map = new HashMap<>(2 * m);
        //存储唯一关系组 一个long可存储一组关系
        Set<Long> set = new HashSet<>(2 * m);

        for (int i = 0; i < m + q; i++) {
            build.append(in.readLine());
            build.append(" ");
        }
        in.close();
        StringTokenizer token = new StringTokenizer(build.toString());
        for (int i = 0; i < m; i++) {
            int a = Integer.valueOf(token.nextToken());
            int b = Integer.valueOf(token.nextToken());
            set.add(getId(a, b));
        }
        int[][] ts = new int[q][4];
        for (int i = 0; i < q; i++) {
            int f = Integer.valueOf(token.nextToken());
            int a = Integer.valueOf(token.nextToken());
            int b = Integer.valueOf(token.nextToken());
            ts[i][0] = f;
            ts[i][1] = a;
            ts[i][2] = b;
            if (f == 1) {
                long c = getId(a, b);
                if (set.remove(c)) {
                    ts[i][3] = 1;
                }
            }
        }
         long mod = Integer.MAX_VALUE;
        for (long l : set) {       
            int a = (int)(l & mod);
            int b = (int)((l >> 32) & mod);
            int pa = getP(map, a);
            int pb = getP(map, b);
            map.put(pa, pb);
        }
        build = new StringBuilder();
        for (int i = q - 1; i >= 0; i--) {
            int f = ts[i][0];
            int a = ts[i][1];
            int b = ts[i][2];      
            if (f == 1) {            
                if (ts[i][3] == 1) {
                int pa = getP(map, a);
                int pb = getP(map, b);
                map.put(pa, pb);
                }
                continue;
            }
            int pa = getP(map, a);
            int pb = getP(map, b);
            String sm = (pa == pb) ? "Yes" : "No";
            build.insert(0, '\n');
            build.insert(0, sm);
        }
        //快速输出
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));
        writer.write(build.toString());
        writer.flush();
    }

    public static int getP(Map<Integer, Integer> map, int i) {
        
        if (!map.containsKey(i)) {
            map.put(i, i);
            return i;
        }
        int p = map.get(i);
        if (p != i) {
            p = getP(map, p);
            map.put(i, p);
        }
        return p;
    }

    public static long getId(int a, int b) {
            //排序去重
        if (a < b) {
            int mid = b;
            b = a;
            a = mid;
        }
        long r = a;
        r = r << 32;
        r = r | b;
        return r;
    }

}


编辑于 2025-03-24 16:39:18 回复(0)
正难则反,删边变成加边,需要注意的是,反过来加的边不一定是初始时候的边,用set记录一下删除的边,添加的时候判断这个边是不是原来需要加的
package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	in, out := bufio.NewReader(os.Stdin), bufio.NewWriter(os.Stdout)
	defer out.Flush()
	var n, m, q, op, u, v int
	type record struct {
		op, u, v int
	}
	fmt.Fscan(in, &n, &m, &q)
	type fr struct {
		u, v int
	}
	frs := map[fr]struct{}{}
	for i := 0; i < m; i++ {
		fmt.Fscan(in, &u, &v)
		if u > v {
			u, v = v, u
		}
		frs[fr{u, v}] = struct{}{}
	}
	qs := make([]record, q)
	removedEdges := map[fr]struct{}{}
	for ; q > 0; q-- {
		fmt.Fscan(in, &op, &u, &v)
		qs[q-1] = record{op, u, v}
		if op == 1 {
			if u > v {
				u, v = v, u
			}
			removedEdges[fr{u, v}] = struct{}{}
		}
	}
	// 初始化并查集
	fa := map[int]int{}
	sz := map[int]int{}
	var find func(int) int
	find = func(x int) int {
		if _, ok := fa[x]; !ok {
			// 初始化
			fa[x] = x
			sz[x] = 1
		}
		if x != fa[x] {
			fa[x] = find(fa[x])
		}
		return fa[x]
	}
	union := func(i, j int) {
		x, y := find(i), find(j)
		if x == y {
			return
		}
		if sz[x] > sz[y] {
			x, y = y, x
		}
		sz[y] += sz[x]
		fa[x] = y
	}
	same := func(i, j int) bool {
		x, y := find(i), find(j)
		return x == y
	}
	for k, _ := range frs {
		if _, ok := removedEdges[k]; !ok {
			union(k.u, k.v)
		}
	}
	ans := make([]bool, 0)
	for _, qq := range qs {
		op, u, v = qq.op, qq.u, qq.v
		if op == 2 {
			// 查询
			ans = append(ans, same(u, v))
		} else {
			// 原本存在再加入
			if u > v {
				u, v = v, u
			}
			if _, ok := frs[fr{u, v}]; ok {
				union(u, v)
			}
		}
	}
	for i := len(ans) - 1; i >= 0; i-- {
		if ans[i] {
			fmt.Fprintln(out, "Yes")
		} else {
			fmt.Fprintln(out, "No")
		}
	}
}

func max(a, b int) int {
	if a < b {
		return b
	}
	return a
}
func min(a, b int) int {
	if a > b {
		return b
	}
	return a
}



发表于 2025-03-06 20:23:42 回复(0)
#include<iostream>
#include<vector>
#include<cmath>
#include<unordered_map>
#include<set>

using namespace std;

struct HashFunc
{
	template<typename T, typename U>
	size_t operator()(const std::pair<T, U>& p) const {
		return std::hash<T>()(p.first) ^ std::hash<U>()(p.second);
	}
};
unordered_map<int,int> parent;
set<pair<int,int>> edges;
vector<tuple<int,int,int>> query;
unordered_map<pair<int,int>,int,HashFunc> del;
vector<string> ans;

int find(int x){
    if(parent[x]!=x){
        parent[x]=find(parent[x]);
    }
    return parent[x];
}
void Union(int p,int q){
    parent[find(p)]=find(q);
}
bool is_connect(int p,int q){
    return find(p)==find(q);
}

int main(){
    int n,m,q;
    cin>>n>>m>>q;
    pair<int,int> edge; 
    for(int i=0;i<m;i++){
        int u,v;
        cin>>u>>v;
        if(u>v) swap(u,v);
        parent[u]=u;
        parent[v]=v;
        edges.insert({u,v});
    }
    for(int i=0;i<q;i++){
        int op,u,v;
        cin>>op>>u>>v;
        if(u>v){
            swap(u,v);
        }
        parent[u]=u;
        parent[v]=v;
        query.push_back({op,u,v});
        if(op==1){
            if(del.count({u,v})==0){
                del[{u,v}]=i;
            }
        }
    }
    for(auto tmp:edges){
        if(del.count(tmp)){
            continue;
        }
        auto[a,b]=tmp;
        Union(a,b);
    }

    for(int i=q-1;i>=0;i--){
        auto [a,b,c]=query[i];
        if(a==2){
            ans.push_back(is_connect(b,c)?"Yes":"No");
        }else{
            if(del.count({b,c})){
                int time=del[{b,c}];
                if(edges.count({b,c})){
                    if(i==time){
                        Union(b,c);
                    }
                }
            }
        }
    }
    int size=ans.size();
    for(int i=size-1;i>=0;i--){
        cout<<ans[i]<<endl;
    }
    

    return 0;
}

发表于 2024-08-23 18:29:13 回复(0)
一直调试不过去,一开始用set,后来才发现需要对遗忘计数。
解题思路
  1. 朋友的朋友可以介绍,所以要使用并查集;因为连通图不具备快速查询弱关系的能力。
  2. 关系不断地删除,并查集不能处理删除问题,所以用反向并查集。先确定最终状态,再倒过来应用操作,淡忘关系变成建立关系。
  3. 由于是10^9,所以需要使用哈希表。
踩坑过程
  1. 并查集要将每个人的祖先初始化为自身,如果采用哈希表,则一定要在使用之前将仅使用的元素初始化好。可以把初始化过程写到find()函数中。
  2. 被遗忘的关系可以被遗忘多次,即存在负数次遗忘。因此将操作逆转过来不是简单地将操作数组reverse,而是要记住每对关系的遗忘次数,如果是负数,则要等关系次数恢复到1时,再真正地建立关系。
  3. 为了避免在哈希计数表中存储两遍edge,此处对make_pair(u, v)内部排序,使得u <= v总是成立。因此,在涉及到哈希计数表的访问和写入时,都要对u, v进行排序,确保这个约束存在。
代码
struct dsu {
  unordered_map<ULL, ULL> fa;

  ULL find(ULL x) {
    auto it = fa.find(x);
    if (it == fa.end()) {
      fa[x] = x;
      return x;
    }
    return it->second == x ? x : (fa[x] = find(it->second));
  }

  void unite(ULL x, ULL y) {
    x = find(x);
    y = find(y);
    fa[y] = x;
  }

  bool isUnited(ULL x, ULL y) {
    return find(x) == find(y);
  }
};

int main() {
  ULL n;
  int m, q;
  cin >> n >> m >> q;
  map<pair<ULL, ULL>, int> relations;
  ULL u, v;
  for (int i = 0; i < m; i++) {
    cin >> u >> v;
    if (u > v) swap(u ,v);
    relations[{u, v}]++;
  }
  
  vector<tuple<int, ULL, ULL>>ops;
  ULL op;
  for (int i = 0; i < q; i++) {
    cin >> op >> u >> v;
    ops.push_back({op, u, v});
    if (u > v) swap(u, v);
    if (op == 1) relations[{u, v}]--;
  }

  dsu dset;
  for (auto& [key, val]: relations) {
    if (val > 0) {
      dset.unite(key.first, key.second);
    }
  }

  stack<bool> res;
  for (int i = q-1; i >= 0; i--) {
    auto& [op, u, v] = ops[i];
    if (op == 2) {
      res.push(dset.isUnited(u, v));
    } else if (op == 1) {
      if (u > v) swap(u, v);
      relations[{u, v}]++;
      if (relations[{u, v}] == 1) dset.unite(u, v);
    }
  }
  while (!res.empty()) {
    cout << (res.top() ? "Yes" : "No") << endl;
    res.pop();
  }

  return 0;
}



编辑于 2024-08-18 23:47:42 回复(0)

离线反向并查集 + 离散化

注意细节很多
很无语的就是当查询这种情况的时候要输出,一开始没加这个条件就了,加了才过,搞得我都怀疑自己的并查集是不是写错了,搞了好久,最终选择怀疑题目有坑才过了。

#include 
#define _1 first
#define _2 second
using i64 = long long;
const int N = 2e5 + 5, M = 5e5 + 5;
const int mod = 1e9 + 7, MOD = 998244353;
int n, m, k;
std::pair> a[N];
std::map, bool> mp;
std::map, int> cnt;
std::unordered_map pos;

class DSU {
    std::vector p, sz;

public:
    DSU(int n) : p(n + 5), sz(n + 5, 1) { std::iota(p.begin(), p.end(), 0);   }
    int find(int x) {   return p[x] == x ? x : p[x] = find(p[x]);   }
    bool same(int x, int y) {   return find(x) == find(y);  }

    bool merge(int x, int y){
        x = find(x), y = find(y);
        if (x == y) return false;
        if (sz[x] < sz[y]) std::swap(x, y);
        sz[x] += sz[y];
        p[y] = x;
        return true;
    }
};
void solve () {
    std::cin >> n >> m >> k;
    int num = 0, idx = 0;
    for (int i = 1; i <= m; i ++) {
        int u, v;
        std::cin >> u >> v;

        if (pos[u] == 0)    pos[u] = ++ num;
        if (pos[v] == 0)    pos[v] = ++ num;

        u = pos[u]; v = pos[v];
        if (u > v)  std::swap(u, v);
        mp[{u, v}] = true ;
    }

    for (int i = 1; i <= k; i ++) {
        int op, u, v;
        std::cin >> op >> u >> v;
        u = pos[u]; v = pos[v];
        if (u > v)  std::swap(u, v);
        if (op == 1) {
            if(mp[{u, v}]) {
                cnt[{u, v}] ++ ;
                a[++ idx] = {op, {u, v}};
            }
        }
        else    a[++ idx] = {op, {u, v}};
    }
    DSU dsu(num);
    for (auto [p, res] : mp) {
        if (cnt[p] || res == false)    continue ;
        dsu.merge(p._1, p._2);
    }
    std::stack ans;
    for (int i = idx; i; i --) {
        int op = a[i]._1, u = a[i]._2._1, v = a[i]._2._2;
        if (op == 1) {
            cnt[{u, v}] -- ;
            if (cnt[{u, v}])    continue ;
            dsu.merge(u, v);
        }
        else {
            if (u == v) ans.push("No");
            else    ans.push(dsu.same(u, v) ? "Yes" : "No");
        }
    }
    while (ans.size()) {
        std::cout << ans.top() << '\n';
        ans.pop();
    }
    return ;
}

编辑于 2024-08-09 16:42:41 回复(0)