codeforces 并查集_Codeforces 1166F 并查集 启发式合并

题意:给你一张无向图,无向图中每条边有颜色。有两种操作,一种是询问从x到y是否有双彩虹路,一种是在x到y之间添加一条颜色为z的边。双彩虹路是指:如果给这条路径的点编号,那么第i个点和第i - 1个点相连的边与第i个点和第i + 1个点相连的边颜色一样,其中i是偶数。

思路:这个问题相当于初了最后一步没有限制以外(最后一步只走一条边),每一步都要走颜色相同的两条边。我们先不考虑最后一步的问题,只考虑每一步都要走颜色相同的两条边,那么我们只需要扫描每一个点的出边,如果有两条边颜色相同,就在一张新图上把这两条边除这个点以外的端点相连,这样询问就变成了判断两个点是否在一个连通块中。因为只是判断是否在同一连通块中,我们不需要建图,用并查集就可以了。现在需要考虑最后一步的问题,我们可以用一个set维护与这个节点直接相邻的点,这样对于询问x, y,如果y在连通块x的相邻节点中,也可以。对于加边的操作,相当于并查集的合并,但是同时需要合并的还有set, 我们合并的时候需要判断一下两个集合的大小,把小的插入大的里面,这样可以保证每次集合合并复杂度是O(logn * logn)的。

代码:

#include

using namespace std;

const int maxn = 100010;

set s[maxn];

set::iterator it1;

set > edge[maxn];

set >::iterator it;

int f[maxn];

int get(int x) {

if(x == f[x]) return x;

return f[x] = get(f[x]);

}

void merge(int x, int y) {

int x1 = get(x), y1 = get(y);

if(x1 == y1) return;

if(s[x1].size() < s[y1].size()) swap(x1, y1);

for (it1 = s[y1].begin(); it1 != s[y1].end(); it1++) {

s[x1].insert(*it1);

}

s[y1].clear();

f[y1] = x1;

}

void add(int x, int y, int z) {

s[get(x)].insert(y);

it = edge[x].lower_bound(make_pair(z, -1));

if(it == edge[x].end() || it -> first != z) {

edge[x].insert(make_pair(z, y));

} else {

merge(it -> second, y);

}

}

int main() {

int x, y, z, n, m, c, T;

char op[5];

scanf("%d%d%d%d", &n, &m, &c, &T);

for (int i = 1; i <= n; i++) f[i] = i;

for (int i = 1; i <= m; i++) {

scanf("%d%d%d", &x, &y, &z);

add(x, y, z);

add(y, x, z);

}

while(T--) {

scanf("%s", op + 1);

if(op[1] == '+') {

scanf("%d%d%d", &x, &y, &z);

add(x, y, z);

add(y, x, z);

} else {

scanf("%d%d", &x, &y);

if(get(x) == get(y)) {

printf("Yes\n");

} else if(s[get(x)].find(y) != s[get(x)].end()) {

printf("Yes\n");

} else {

printf("No\n");

}

}

}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值