并查集
并查集
[NOI2001] 食物链
题目描述
动物王国中有三类动物 \(A,B,C\),这三类动物的食物链构成了有趣的环形。\(A\) 吃 \(B\),\(B\) 吃 \(C\),\(C\) 吃 \(A\)。
现有 \(N\) 个动物,以 \(1 \sim N\) 编号。每个动物都是 \(A,B,C\) 中的一种,但是我们并不知道它到底是哪一种。
有人用两种说法对这 \(N\) 个动物所构成的食物链关系进行描述:
- 第一种说法是
1 X Y
,表示 \(X\) 和 \(Y\) 是同类。 - 第二种说法是
2 X Y
,表示 \(X\) 吃 \(Y\)。
此人对 \(N\) 个动物,用上述两种说法,一句接一句地说出 \(K\) 句话,这 \(K\) 句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。
- 当前的话与前面的某些真的话冲突,就是假话;
- 当前的话中 \(X\) 或 \(Y\) 比 \(N\) 大,就是假话;
- 当前的话表示 \(X\) 吃 \(X\),就是假话。
你的任务是根据给定的 \(N\) 和 \(K\) 句话,输出假话的总数。
输入格式
第一行两个整数,\(N,K\),表示有 \(N\) 个动物,\(K\) 句话。
第二行开始每行一句话(按照题目要求,见样例)
输出格式
一行,一个整数,表示假话的总数。
样例 #1
样例输入 #1
100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5
样例输出 #1
3
提示
对于全部数据,\(1\le N\le 5 \times 10^4\),\(1\le K \le 10^5\)。
扩展域并查集
std
#include<bits/stdc++.h>
using namespace std;
const int N = 5e4+9;
int n,m,cnt;
int fa[N*3];
int get(int x)
{
if(fa[x] == x)return x;
return fa[x] = get(fa[x]);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1;i <= n*3;i++)fa[i] = i;
//A:1~n B:n+1~2*n c:n*2+1~n*3
while(m--)
{
int op,x,y;
scanf("%d%d%d",&op,&x,&y);
int fx = get(x),fy = get(y);
if(x > n || y > n)cnt++;
else if(op == 1)
{
if(fx == get(y+n) || fy == get(x+n))cnt++;
else
{
fa[fy] = fx;
fa[get(y+n)] = get(x+n);
fa[get(y+2*n)] = get(x+2*n);
}
}
else
{
if(x == y || fx == fy || fy == get(x+n))cnt++;
else
{
fa[get(y+n)] = fx;
fa[get(y+2*n)] = get(x+n);
fa[fy] = get(x+2*n);
}
}
}
printf("%d",cnt);
return 0;
}
带权并查集
维护同一连通块中关系有传递性的点
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5+9;
int pa[N],r[N],n,k,ans,x,y,d;
int get(int x)
{
if(x != pa[x])
{
int fx = get(pa[x]);
r[x] = (r[x]+r[pa[x]])%3;
pa[x] = fx;
}
return pa[x];
}
bool merge(int d,int x,int y)
{
int fx=get(x),fy=get(y);
if (fx==fy)
if ((r[y]-r[x]+3)%3!=d) return 1;
else return 0;
pa[fy]=fx;
r[fy]=(r[x]-r[y]+d+3)%3;
return 0;
}
int main()
{
scanf("%d %d",&n,&k);
for (int i=1;i<=n;i++) pa[i]=i;
for (int i=1;i<=k;i++)
{
scanf("%d %d %d",&d,&x,&y);
if(x>n||y>n||(x==y && d==2))
{
ans++;
continue;
}
if (merge(d-1,x,y)) ans++;
}
printf("%d",ans);
}
[CEOI1999] Parity Game
题目描述
Alice 和 Bob 在玩一个游戏:他写一个由 \(0\) 和 \(1\) 组成的序列。Alice 选其中的一段(比如第 \(3\) 位到第 \(5\) 位),问他这段里面有奇数个 \(1\) 还是偶数个 \(1\)。Bob 回答你的问题,然后 Alice 继续问。Alice 要检查 Bob 的答案,指出在 Bob 的第几个回答一定有问题。有问题的意思就是存在一个 \(01\) 序列满足这个回答前的所有回答,而且不存在序列满足这个回答前的所有回答及这个回答。
输入格式
第 \(1\) 行一个整数 \(n\),是这个 \(01\) 序列的长度。
第 \(2\) 行一个整数 \(m\),是问题和答案的个数。
第 \(3\) 行开始是问题和答案,每行先有两个整数,表示你询问的段的开始位置和结束位置。然后是 Bob 的回答。odd
表示有奇数个 \(1\),even
表示有偶数个 \(1\)。
输出格式
输出一行,一个数 \(x\),表示存在一个 \(01\) 序列满足第 \(1\) 到第 \(x\) 个回答,但是不存在序列满足第 \(1\) 到第 \(x+1\) 个回答。如果所有回答都没问题,你就输出所有回答的个数。
样例 #1
样例输入 #1
10
5
1 2 even
3 4 odd
5 6 even
1 6 even
7 10 odd
样例输出 #1
3
提示
对于 \(100\%\) 的数据,\(1 \le n \leq 10^9\),\(m \leq 5 \times 10^3\)。
带权并查集
#include<bits/stdc++.h>
using namespace std;
const int N = 5e3+9;
int n,m;
unordered_map<int,int> mp;
struct Q
{
int l,r;
bool ans;
}a[N];
int t[N<<1],k;
char ch[5];
int fa[N];
bool r[N];
int get(int x)
{
if(x != fa[x])
{
int fx = get(fa[x]);
r[x] = r[x]^r[fa[x]];
fa[x] = fx;
}
return fa[x];
}
void merge(int x,int y,int ans)
{
int fx = get(x),fy = get(y);
if(fx != fy)
{
fa[fx] = fy;
r[fx] = r[x]^ans^r[y];
}
return;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1;i <= m;i++)
{
scanf("%d%d",&a[i].l,&a[i].r);
t[++k] = a[i].l-1,t[++k] = a[i].r;
scanf("%s",ch);
a[i].ans = ch[0]=='o';
}
sort(t+1,t+1+k);
k = unique(t+1,t+1+k)-t-1;
for(int i = 1;i <= k;i++)mp[t[i]] = i,fa[i] = i;
for(int i = 1;i <= m;i++)
{
int x = mp[a[i].l-1],y = mp[a[i].r],ans = a[i].ans;
int fx = get(x),fy = get(y);
if(fx == fy && (r[x]^r[y]) != ans)printf("%d",i-1),exit(0);
else merge(x,y,ans);
}
printf("%d",m);
return 0;
}
扩展域并查集
#include<bits/stdc++.h>
using namespace std;
const int N = 5e3+9;
int n,m;
unordered_map<int,int> mp;
struct Q
{
int l,r;
bool ans;
}a[N];
int t[N<<1],k;
char ch[5];
int fa[N<<1];
int get(int x)
{
return x==fa[x] ? x : get(fa[x]);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1;i <= m;i++)
{
scanf("%d%d",&a[i].l,&a[i].r);
t[++k] = a[i].l-1,t[++k] = a[i].r;
scanf("%s",ch);
a[i].ans = ch[0]=='o';
}
sort(t+1,t+1+k);
k = unique(t+1,t+1+k)-t-1;
for(int i = 1;i <= k;i++)mp[t[i]] = i;
for(int i = 1;i <= k<<1;i++)fa[i] = i;
for(int i = 1;i <= m;i++)
{
int x = mp[a[i].l-1],y = mp[a[i].r],ans = a[i].ans;
int x_= x+k,y_ = y+k;
if(ans)
{
if(get(x)==get(y))printf("%d",i-1),exit(0);
fa[get(x)] = get(y_);
fa[get(x_)] = get(y);
}
else
{
if(get(x) == get(y_))printf("%d",i-1),exit(0);
fa[get(x)] = get(y);
fa[get(x_)] = get(y_);
}
}
printf("%d",m);
return 0;
}
并查集维护序列连通性
P2391 白雪皑皑
题目背景
“柴门闻犬吠,风雪夜归人”,冬天,不期而至。千里冰封,万里雪飘。空中刮起了鸭毛大雪。雪花纷纷,降落人间。 美能量星球(pty 在 spore 上的一个殖民地)上的人们被这美景所震撼。但是 pty 却不高兴,他不喜欢白色的世界,他觉得这样太单调了。所以他想对雪花进行染色,让世界变得多彩些。
题目描述
现在有 \(n\) 片雪花排成一列。 pty 要对雪花进行 \(m\) 次染色操作,第 \(i\) 次染色操作中,把第 \(((i\times p+q)\bmod n)+1\) 片雪花和第 \(((i\times q+p)\bmod n)+1\) 片雪花之间的雪花(包括端点)染成颜色 \(i\)。其中 \(p,q\) 是给定的两个正整数。他想知道最后 \(n\) 片雪花被染成了什么颜色。没有被染色输出 \(0\)。
输入格式
输入共四行,每行一个整数,分别为 \(n,m,p,q\),意义如题中所述。
输出格式
输出共 \(n\) 行,每行一个整数,第 \(i\) 行表示第 \(i\) 片雪花的颜色。
样例 #1
样例输入 #1
4
3
2
4
样例输出 #1
2
2
3
0
提示
- 对于 \(20\%\) 的数据满足:\(n,m\leq 1000\)。
- 对于 \(40\%\) 的数据满足:\(n\leq 8000\),\(m\leq 10^6\)。
- 对于 \(80\%\) 的数据满足:\(n\leq 5\times 10^5\),\(m\leq 10^7\)。
- 对于 \(100\%\) 的数据满足:\(1\leq n\leq 10^6\),\(1\leq m\leq 10^7\)。
保证 \(1\leq m\times p+q,m\times q+p\leq 2\times 10^9\)。
并查集可以维护图的连通性,当然类比到序列上也是可行的
用 \(fa[x]\) 表示 \(x\) 后第一个可操作的节点
倒序覆盖跳跃染色
std
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+9;
int n,m,p,q;
int fa[N],col[N];
int get(int x)
{
return x == fa[x] ? x : fa[x] = get(fa[x]);
}
pair<int,int> a[N];
int main()
{
scanf("%d%d%d%d",&n,&m,&p,&q);
for(int i = 1;i <= n+1;i++)fa[i] = i;
for(int i = m;i >= 1;i--)
{
int l = (i*p+q)%n+1,r = (i*q+p)%n+1;
if(l > r)swap(l,r);
int t = get(l);
while(t <= r)
{
col[t] = i;
fa[t] = get(t+1);
t = get(t);
}
}
for(int i = 1;i <= n;i++)printf("%d\n",col[i]);
return 0;
}