给出节点数为\(n\),边数为\(m\)的图。
保证每个点对都是互连的。
定义彩虹路:这条路经过\(k\)个节点,对于\(x(x\%2=0)\)的节点,左右两条边颜色相同。
现在有\(q\)次操作。
第一种操作是添加一条边。
第二种操作是回答是否能经过彩虹边从\(a\)节点到达\(b\)节点。
做法:
能相互到达的点用并查集连起来。
具体做法就是:
当\(a-b-c\)的边的颜色相同时,我们把\(a\)和\(c\)节点用合并,代表\(a\)和\(c\)互连。
对于节点\(a\),每个颜色的边只需要保存一条,这样可以快速合并节点。
这里可以枚举b的边集,按序合并即可。
但是还有一种情况,\(x\)节点和\(y\)节点经过的边数为奇数,这样最后一条边的颜色就不重要了。
所以如果两个节点不在一个集合内,就需要看其中一个点能否通过另一个集合到达。
对每个集合维护一个set,然后枚举每条边,把这条边两端的节点塞进另一个节点的集合的set。
加边的时候,对边两边的节点维护并查集。
合并集合的时候,对集合对应的set,启发式合并即可。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
int n,m,c,q;
map<int,int> g[maxn];
set<int> st[maxn];
int father[maxn];
int findfather (int x) {
int a=x;
while (x!=father[x]) x=father[x];
while (a!=father[a]) {
int z=a;
a=father[a];
father[z]=x;
}
return x;
}
void un (int x,int y) {
x=findfather(x);
y=findfather(y);
if (x==y) return;
if (st[x].size()<st[y].size()) {
father[x]=y;
for (auto it:st[x]) st[y].insert(it);
}
else {
father[y]=x;
for (auto it:st[y]) st[x].insert(it);
}
}
int main () {
scanf("%d%d%d%d",&n,&m,&c,&q);
for (int i=1;i<=n;i++) father[i]=i;
while (m--) {
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
st[findfather(y)].insert(x);
st[findfather(x)].insert(y);
if (g[x].count(z)) {
un(g[x][z],y);
}
else {
g[x][z]=y;
}
if (g[y].count(z)) {
un(g[y][z],x);
}
else {
g[y][z]=x;
}
}
while (q--) {
char op;
getchar();
scanf("%c",&op);
if (op=='+') {
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
st[findfather(y)].insert(x);
st[findfather(x)].insert(y);
if (g[x].count(z)) {
un(g[x][z],y);
}
else {
g[x][z]=y;
}
if (g[y].count(z)) {
un(g[y][z],x);
}
else {
g[y][z]=x;
}
}
else {
int x,y;
scanf("%d%d",&x,&y);
if (findfather(x)==findfather(y)) {
printf("Yes\n");
}
else if (st[findfather(x)].count(y)) {
printf("Yes\n");
}
// else if (st[findfather(y)].count(x)) {
// printf("Yes\n");
// }
else {
printf("No\n");
}
}
}
}