並查集擴充套件
目錄
- 並查集擴充套件
- 普通並查集
- 例題:
- 1.洛谷P1197 星球大戰
- 2.洛谷P1955 程式自動分析
- 例題:
- 帶權並查集
- 例題:
- 1.洛谷P2024 食物鏈
- 2.洛谷P1196 銀河英雄傳說
- 3.洛谷P5937 Parity Game
- 例題:
- 擴充套件域並查集
- 例題:
- 1.洛谷P1525 關押罪犯
- 例題:
- 普通並查集
普通並查集
例題:
1.洛谷P1197 星球大戰
連結:
[P1197 JSOI2008] 星球大戰 - 洛谷 | 電腦科學教育新生態 (luogu.com.cn)
型別:
普通並查集+反向加點
解析:
正面刪邊很難,考慮反向求解,先求刪完所有要求點的所有連通塊數量,然後再一個一個加入,將問題簡化
程式碼:
const int N = 400005;
struct edges{
int v,ne;
}e[N << 1];
int h[N],idx = 0;
void add(int u,int v){
e[idx] = {v,h[u]};
h[u] = idx++;
}
int f[N];
int fd(int x){
if(x==f[x]) return x;
return f[x] = fd(f[x]);
}
int vis[N],ans[N];
int n,m;
void solve(){
memset(h,-1,sizeof h);
cin >> n >> m;
for(int i = 1;i<=n;i++) f[i] = i;
vector<int> b;
for(int i = 1;i<=m;i++){
int u,v;
cin >> u >> v;
u++,v++;
add(v,u);
add(u,v);
}
int k;
cin >> k;
int res = n - k;
for(int i = 0;i<k;i++){
int t;
cin >> t;
t++;
vis[t] = 1;
b.push_back(t);
}
for(int i = 1;i<=n;i++){
if(vis[i]) continue;
for(int j = h[i];~j;j=e[j].ne){
int v = e[j].v;
if(vis[v]) continue;
if(f[fd(i)] != f[fd(v)]){
res--;
f[fd(i)] = f[fd(v)];
}
}
}
ans[k+1] = res;
for(int i = k - 1;i >= 0;i--){
vis[b[i]] = 0;
res++;
for(int j = h[b[i]];~j;j=e[j].ne){
int v = e[j].v;
if(vis[v]) continue;
if(f[fd(b[i])] != f[fd(v)]){
res--;
f[fd(b[i])] = f[fd(v)];
}
}
ans[i + 1] = res;
}
for(int i = 1;i<=k+1;i++){
cout << ans[i] << endl;
}
}
2.洛谷P1955 程式自動分析
連結:
[P1955 NOI2015] 程式自動分析 - 洛谷 | 電腦科學教育新生態 (luogu.com.cn)
型別:
普通並查集+離散化
解析:
普通並查集判斷是否矛盾,資料大進行離散化
程式碼:
const int N = 200005;
struct DSU {
vector<int> f;
DSU(){}
DSU(int n) {
init(n);
}
void init(int n) {
f.resize(n);
iota(f.begin(), f.end(), 0);
}
int fd(int x) {
if(f[x]==x) return x;
return f[x] = fd(f[x]);
}
bool same(int x, int y) {
return fd(x) == fd(y);
}
bool mg(int x, int y) {
x = fd(x);
y = fd(y);
if (x == y)return false;
f[y] = x;
return true;
}
};
int gt_idx(vector<int>& a,int i){
int t = lower_bound(a.begin(),a.end(),i) - a.begin() + 1;
return t;
}
void solve(){
int n;
cin >> n;
vector<int> v1;
vector<pair<int,int>> ys;
vector<pair<int,int>> ns;
vector<int> a;
for(int i = 0;i < n;i++){
int u,v,op;
cin >> u >> v >> op;
if(op==1){
ys.push_back({u,v});
}else{
ns.push_back({u,v});
}
a.push_back(u);
a.push_back(v);
}
sort(a.begin(),a.end());
a.erase(unique(a.begin(),a.end()),a.end());
int sz = a.size();
DSU d(sz + 1);
for(auto &[u,v]:ys){
d.mg(gt_idx(a,u),gt_idx(a,v));
}
for(auto &[u,v]:ns){
if(d.fd(gt_idx(a,u)) == d.fd(gt_idx(a,v))){
cout << "NO" << endl;
return;
}
}
cout << "YES" << endl;
}
帶權並查集
例題:
1.洛谷P2024 食物鏈
連結:
[P2024 NOI2001] 食物鏈 - 洛谷 | 電腦科學教育新生態 (luogu.com.cn)
型別:
帶權並查集
解析:
維護%3關係,考慮合併時候維護集合關係,在同類時候d[u] - d[v]在%3意義下一定是0,x吃y那麼一定是1,題目無需考慮2的情況,因為沒有設定y吃x的正確性
程式碼:
const int N = 200005;
int f[N],d[N];
int n,m;
int fd(int x){
if(x!=f[x]){
int t = f[x];
f[x] = fd(f[x]);
d[x] += d[t];
}
return f[x];
}
int calc(int x,int y){
return ((x-y)%3 + 3)%3;
}
void merge(int x,int y,int v){
int px = fd(x),py = fd(y);
f[px] = py;
d[px] = d[y] - d[x] + v;
}
void solve(){
cin >> n >> m;
iota(f,f+n+1,0);
int cnt = 0;
while(m--){
int u,v,op;
cin >> op >> u >> v;
if(u>n||v>n){
cnt++;
continue;
}
if(op==1){
if(fd(u)==fd(v) && calc(d[u],d[v]) != 0){
cnt++;
continue;
}
//d[v] = d[u] + d[fd(u)]
//d[fd[u]] = d[v] - d[u]
if(fd(u)!=fd(v)) merge(u,v,0);
}else{
if(fd(u)==fd(v) && calc(d[u],d[v]) != 1){
cnt++;
continue;
}
//d[v] = d[u] - 1 + d[fd(u)]
//d[fd[u]] = d[v] - d[u] + 1
if(fd(u)!=fd(v)) merge(u,v,1);
}
}
cout << cnt << endl;
}
2.洛谷P1196 銀河英雄傳說
連結:
[P1196 NOI2002] 銀河英雄傳說 - 洛谷 | 電腦科學教育新生態 (luogu.com.cn)
型別:
帶權並查集
解析:
維護兩個變數,一個是集合大小,一個是元素到根的距離,然後操作即可
程式碼:
const int N = 200005;
int sz[N],d[N],f[N];
void init(){
for(int i = 0; i < N - 1;i++){
f[i] = i;
sz[i] = 1;
}
}
int fd(int x){
if(f[x]!=x){
int t = f[x];
f[x] = fd(f[x]);
d[x] += d[t];
}
return f[x];
}
void merge(int x,int y){
int px = fd(x),py = fd(y);
f[px] = py;
d[px] = sz[py];
sz[py] += sz[px];
}
void solve(){
int q;
init();
cin >> q;
while(q--){
int x,y;
char op;
cin >> op >>x >> y;
if(op=='C'){
if(fd(x)!=fd(y)){
cout <<-1<<endl;
}else{
cout << abs(d[x]-d[y]) - 1 << endl;
}
}else{
merge(x,y);
}
}
}
3.洛谷P5937 Parity Game
連結:
[P5937 CEOI1999] Parity Game - 洛谷 | 電腦科學教育新生態 (luogu.com.cn)
型別:
帶權並查集
解析:
字首和思想,合併 y 與 x - 1 判斷奇偶性即可
程式碼:
const int N = 200005;
int n,q;
int idx = 0;
map<int,int> h;
int gt(int x){
if(h.count(x)) return h[x];
h[x] = ++idx;
return idx;
}
int d[N],f[N];
int fd(int x){
if(f[x] != x){
int t = f[x];
f[x] = fd(f[x]);
d[x] += d[t];
}
return f[x];
}
void merge(int x,int y,int v){
int px = fd(x),py = fd(y);
f[px] = py;
d[px] = d[y] - d[x] + v;
}
int calc(int x,int y){
return ((d[x] - d[y])%2 + 2)%2;
}
void solve(){
cin >> n >> q;
int res = q;
for(int i = 0;i<N;i++) f[i] = i;
int id = 0;
while(q--){
int x,y;
++id;
string op;
cin >> x >> y >> op;
if(x==y&&op=="even"){
res = id-1;
break;
}
x = gt(x - 1);
y = gt(y);
if(op=="even"){
if(fd(x)==fd(y)){
if(calc(x,y) == 1){
res = id - 1;
break;
}
}else{
merge(x,y,0);
}
}else{
if(fd(x)==fd(y)){
if(calc(x,y) == 0){
res = id - 1;
break;
}
}else{
merge(x,y,1);
}
}
}
cout << res << endl;
}
擴充套件域並查集
例題:
1.洛谷P1525 關押罪犯
連結:
[P1525 NOIP2010 提高組] 關押罪犯 - 洛谷 | 電腦科學教育新生態 (luogu.com.cn)
型別:
擴充套件域並查集 + 帶權並查集
解析:
方法一(擴充套件域並查集):
- 兩個集合,從大到小排序,考慮將敵對兩個人分成兩個集合,並且擴充套件域合併,敵人的敵人是朋友,就是設x有兩個敵人 y z, 那麼 z 與 x+n 建邊, x +n 與 y 建邊,透過 x+n這個虛點去連線應該在一個集合中的y與z,如果在同一集合,並且是敵對,那麼直接輸出即可
方法二(帶權並查集):
- 從大到小排序,避免前面的數在一個監獄,將兩個集合合併起來,並且賦權值為1代表兩個數不在同一監獄,於是在後面我們可以判斷,如果兩個數之前已經合併過並且mod操作位0代表,這兩個集合一定要在一個監獄中,那麼直接輸出w,(否則,w會更大)
程式碼:
方法一:
int n,m;
struct qs{
int u,v,w;
bool operator<(const qs& q1)const{
return w > q1.w;
}
};
struct DSU {
vector<int> f, sz;
DSU(){}
DSU(int n) {
init(n);
}
void init(int n) {
f.resize(n);
iota(f.begin(), f.end(), 0);
sz.assign(n, 1);
}
int fd(int x) {
if(f[x]==x) return x;
return f[x] = fd(f[x]);
}
bool mg(int x, int y) {
x = fd(x);
y = fd(y);
if (x == y)return false;
f[y] = x;
return true;
}
};
void solve(){
cin >> n >> m;
DSU d(2*(n+1));
vector<qs> q;
for(int i = 0; i < m;i++){
int u,v,w;
cin >> u >> v >> w;
q.push_back({u,v,w});
}
sort(q.begin(),q.end());
for(int i = 0;i < q.size();i++){
int u = q[i].u,v = q[i].v,w = q[i].w;
if(d.fd(u) == d.fd(v)){
cout << w <<endl;
return;
}
d.mg(u,v+n);
d.mg(u+n,v);
}
cout << 0 << endl;
}
方法二:
struct qs
{
int u,v,w;
bool operator<(const qs &t)const{return w > t.w;};
};
int f[N],d[N];
int n,m;
int fd(int x){
if(x!=f[x]){
int t = f[x];
f[x] = fd(f[x]);
d[x] += d[t];
}
return f[x];
}
int calc(int x,int y){
return ((x-y)%2 + 2)%2;
}
void merge(int x,int y){
int px = fd(x),py = fd(y);
f[px] = py;
d[px] = d[y] - d[x] + 1;
}
void solve(){
cin >> n >> m;
int q = m;
vector<qs> a;
iota(f,f+n+1,0);
while(q--){
int u,v,w;
cin >> u >> v >> w;
a.push_back({u,v,w});
}
sort(a.begin(),a.end());
for(int i = 0; i < m;i++){
int u = a[i].u,v = a[i].v,w = a[i].w;
if(fd(u)==fd(v)){
if(calc(d[u],d[v]) == 0){
cout << w << endl;
return;
}
}else{
merge(u,v);
}
}
cout << 0 <<endl;
}