【SJTUOJ笔记】P1118 Travel

https://acm.sjtu.edu.cn/OnlineJudge/problem/1118
由于题目含有大量的合并和查询操作,很容易想到用并查集来做。关键在于,如何在合并集合的同时维护题目要求的信息。
并查集的特点是,任何操作都可以变化为对集合根的操作。但反过来,却无法把根的状态改变直接反映到下面的每个集合元素中。在本题中,具体体现就是一堆旅行团会被根带着一起旅行,但无法在近似 O(1) O ( 1 ) 的时间修改每个旅行团已经走过的城市个数。因此,这里采用了类似于线段树中lazy标记的思想,先记录根结点带着下面的旅行团走过了几个城市,合并时再下推标记,把 O(n) O ( n ) 的下推操作的次数尽可能减少。

具体如下:

void merge(int x, int y){
    int p = find(x), q = find(y);
    sum[q] += sum[p]; //sum[k]记录k号原始旅行团所在的大旅行团包含多少原始旅行团
    for (int i = 1; i <= n; ++i){ //下推标记
        int t = find(i);
        if (t == p || t == q)
            trl[i] += ex[t]; //ex[k]记录k号原始旅行团所在的大旅行团在合并前走过了多少城市
    }
    ex[p] = ex[q] = 0;
    parent[p] = q;
}
case 'T':
            cin >> x >> y;
            p = find(x); 
            q = find(y); //找到x和y的根
            if (p != q){
                a = nowat[p]; //nowat[k]记录k号原始旅行团在哪个城市
                b = nowat[q]; 
                now[a] = -1; //now[k]记录k号城市的大旅行团根的编号,-1表示没有旅行团在k号城市
                nowat[p] = b;
                ++ex[p]; //加标记
                merge(y, x);
            }
            break;
        case 'Q':
            cin >> x;
            p = find(x);
            cout << nowat[p] << ' ' << sum[p] << ' ' << trl[x] + ex[p] << '\n'; //trl[k]记录k号原始旅行团走过了几个城市
            break;

你可能感兴趣的:(算法与数据结构)