\(0.5 \)の紹介
互いに素なセットは、ノード間の非交差な関係のセットが維持され、ツリーデータ構造。より一般的なアルゴリズムの競争の中で使用されます。チェックして、次のように基本的な形がある設定
\(1 \)いくつかの定義
代表要素
代表的な要素の代わりに、通常はルートノードによって表される集合の要素を指します。図4は、集合の要素を表すノードです。
父ノード
および他の類似のツリーデータ構造、親ノードが特定のノード「上」ノードです。Inと方向は常に父のノードを指している一方で、ピントを確認してください。
\(2 \)基本的な実装の機能互いに素セット
どこコレクションノードを見つけるために、合併のコレクション、要素の代わりに:基本的な互いに素-セットは、一般的に2つの操作を実現します。
初期の互いに素セット
私たちは、一般的に、コレクションの各要素が自分である場合には、初期状態でセット、の各要素の独立したと見られて。
for(rg int i=1;i<=n;i++) fa[i]=i;
上記のコード\(FA [\] \)互いに素なセットの簡単な実現に。前記\(FA [i]が\)の代表番号\(I \)親ノード要素。
代わっ要素を探します
チェックセット構造は、各ノードが、代表的な要素(ルート)自体へのポインタを親ノードへのポインタを有して実現されます。
再帰的な要素の代表者を見つけることが可能です
inline int find(int x){ return fa[x]==x?x:find(fa[x]); }
上記のコードは、操作の要素の代表を達成するために見ていきます。ノードは自分の父親の要素の代表的なものである場合、ノードは、あなたが代表要素を見つけるまで、それ以外の場合は、側面にジャンプアップ、互いに素-セットです。
組み合わせたコレクション
ZCY偉大な神はどこかで話をしました
あなたが2校を統合したい場合、最も簡単な方法は、学校が他の学校の校長教頭の一つになることをできるようにすることです。
マージ互いに素セットの最も簡単な方法は、要素の別のセットの代わりに、代わりに父親のノードのコレクションの要素の一つに代わって話すことですので、
inline void merge(int x,int y){
rg int xx=find(x),yy=find(y);
fa[xx]=yy;
}
同じコレクション内の2つの要素かどうかを照会 - 操作の基本的な操作に基づいて、
代表的な要素は、長い二つの要素が配置されるような要素の同じセットの代わりに、それは、同じセット内の2つの要素を示し、によって定義することができます。
理事会のタイトル羅区P3367
\(3 \)互いに素セットの最適化
パス圧縮
いくつかのように癌以下のようにデータがあなたを作ることが互いに素・セット構成は、非常に奇妙である(太字で要素を表します)
この時点でない場合は、いくつかの最適化では、すべての外観は、歩行の多くを繰り返すことになります。そのため、プログラムは非常にゆっくりと実行されます
我々は唯一の要素間の関係のセットを維持したい、と高校数学の必修ので、それはまた、私たちが直接ノードに、各ノード点の父は要素が配置されている表します。方法を見つけることができ、コレクションが障害を持っていることを教えてくれる
次のように実装:
inline int find(int x){ return fa[x]==x?x:fa[x]=find(fa[x]); }
別の利点を持って、我々は再帰的に同じ時間に代わって、コレクションの要素を見つけ、ノード上のすべてのノードの父は、この要素の上に移動しているだろうこのコードは、要素を表しています。
図は見つけることです\(8 \)を達成する要素の数の効果。
あなたは再帰が一度だけ行うことができ、我々は任意の要素に代わって、コレクション内の次の要素を見つけ、見ることができます。
ランク合併により、
私たちはと組み合わせた2つの互いに素セットを検討し、以下に示します
私たちは、最初のツリーデータ構造の基本的な特性を定義 - ランク
ブックランク、「セットサイズ」の解釈の異なる解釈のために異なるアルゴリズム。この方法の定義については、そのような合併は「ヒューリスティックマージ」と呼ばれています。
組み合わせヒューリスティック基本的な考え方は、クエリのコスト構造の小さな大規模に、わずかな増加をマージすることです
実装:
inline void merge(int x,int y){
rg int xx=find(x),yy=find(y);
if(tag[xx]>tag[yy]) swap(xx,yy);
fa[xx]=yy;
}
理事会のタイトル羅区P3367
\(4 \)の互いに素なセットを拡張します
拡張フィールドの互いに素セット
拡張と互いに素セットは、複数の関係を維持するために、単により多くのオープンスペースです
直接の例その上に
例NOI2001食物連鎖
タイトル説明
动物王国中有三类动物 \(A,B,C\),这三类动物的食物链构成了有趣的环形。\(A\) 吃 \(B\),\(B\) 吃 \(C\),\(C\) 吃 \(A\)。
现有 \(N\) 个动物,以 \(1-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\) 句话,输出假话的总数。
解决办法
开一个三倍空间,分别代表同类(\(A\)区),敌人(\(B\)区),食物(\(C\)区)
\(A,B,C\)三区对应编号如下
对于编号为\(i\)的动物,\(A\)区为\(i\),\(B\)区为\(i+n\),\(C\)区为\(i+2*n\)
初始化时要注意\(A,B,C\)三个区对应编号不同,初始化时要注意对应自己编号
- 冲突的处理方式
由规则可得:
- 对于“同类”的情况,如果给定\(X,Y\)互为敌人时,即为不合法。
对应代码:
if(find(x+n)==find(y)||find(y+n)==find(x)){ ans++;}
- 对于“\(X\)吃\(Y\)”的情况,如果给定\(X,Y\)为同类,或者\(Y\)吃\(X\),即为不合法。
对应代码:
if(find(x)==find(y)||find(x)==find(y+n)){ ans++;}
其他情况直接合并即可。
注意:一定要同时维护三个域的信息,因为这三个域实质上地位是平等的
AC代码:
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <string>
#include <vector>
#include <queue>
#include <stack>
#include <cctype>
#include <iostream>
using namespace std;
#define rg register
#define ll long long
#define ull unsigned long long
inline int read(){
rg int s=0,f=0;
rg char ch=getchar();
while(not isdigit(ch)) f|=(ch=='-'),ch=getchar();
while(isdigit(ch)) s=(s<<1)+(s<<3)+(ch^48),ch=getchar();
return f?-s:s;
}
const int N=5e4+15;
int fa[N*3+5];
int n,k;
int ans;
inline int find(int x){ return fa[x]==x?x:fa[x]=find(fa[x]);}
signed main(){
n=read(),k=read();
for(rg int i=1;i<=n*3;i++){
fa[i]=i;
}
for(rg int i=1;i<=k;i++){
int opt=read(),x=read(),y=read();
if(x>n||y>n){
ans++;
continue;
}
if(opt==1){
if(find(x+n)==find(y)||find(y+n)==find(x)){ ans++;}
else{
fa[find(x)]=find(y);
fa[find(x+n)]=find(y+n);
fa[find(x+n+n)]=find(y+n+n);
}
}else{
if(find(x)==find(y)||find(x)==find(y+n)){ ans++;}
else{
fa[find(x+n)]=find(y);
fa[find(x+n+n)]=find(y+n);
fa[find(x)]=find(y+n+n);
}
}
}
printf("%d",ans);
return 0;
}
边带权并查集
边带权并查集可以维护节点到根的信息,通过路径压缩完成信息的统计
例题 NOI2002 银河英雄传说
公元五八○一年,地球居民迁至金牛座\(\alpha\)第二行星,在那里发表银河联邦创立宣言,同年改元为宇宙历元年,并开始向银河系深处拓展。
宇宙历七九九年,银河系的两大军事集团在巴米利恩星域爆发战争。泰山压顶集团派宇宙舰队司令莱因哈特率领十万余艘战舰出征,气吞山河集团点名将杨威利组织麾下三万艘战舰迎敌。
杨威利擅长排兵布阵,巧妙运用各种战术屡次以少胜多,难免恣生骄气。在这次决战中,他将巴米利恩星域战场划分成\(30000\)列,每列依次编号为\(1, 2, …,30000\)。之后,他把自己的战舰也依次编号为\(1, 2, …, 30000\),让第\(i\)号战舰处于第\(i\)列\((i = 1, 2, …, 30000)\),形成“一字长蛇阵”,诱敌深入。这是初始阵形。当进犯之敌到达时,杨威利会多次发布合并指令,将大部分战舰集中在某几列上,实施密集攻击。合并指令为\(M_{i,j}\) ,含义为第\(i\)号战舰所在的整个战舰队列,作为一个整体(头在前尾在后)接至第\(j\)号战舰所在的战舰队列的尾部。显然战舰队列是由处于同一列的一个或多个战舰组成的。合并指令的执行结果会使队列增大。
然而,老谋深算的莱因哈特早已在战略上取得了主动。在交战中,他可以通过庞大的情报网络随时监听杨威利的舰队调动指令。
在杨威利发布指令调动舰队的同时,莱因哈特为了及时了解当前杨威利的战舰分布情况,也会发出一些询问指令:\(C_{i,j}\)。该指令意思是,询问电脑,杨威利的第\(i\)号战舰与第\(j\)号战舰当前是否在同一列中,如果在同一列中,那么它们之间布置有多少战舰。
ベテランの先輩プログラマとして、あなたがプログラム解析ヤンWeili命令を書き込み、問い合わせラインハルトに答えるように求められます。
最後の戦いが始まった、銀河の歴史はページを回した......
ソリューション
ルートノードまでの距離を維持しながら、同じ列の関係を維持するかどうか
互いに素なセットの下、我々は、各ノードまでの距離がルートで見つけることができる\(DIS [I] + DIS [FA [I] \)
アヒルになったパス+圧縮コードを見つけ、私たちはそう
inline int find(int x){
if( fa[x]==x ) return x;
int root=find(fa[x]);
dis[x]+=dis[fa[x]];
return fa[x]=root;
}
あなたが圧縮パスを使用しない場合、距離が不正確な情報のメンテナンスにつながる、ダブルカウントになることに留意すべきです。だから、エッジ加重互いに素設定されたパス圧縮ことが必要です。
- マージについて
この問題は、必要
「第二に接続全体(フロントの終了後頭)として全体船舶キュー\(J \)艦のキューの末尾。」
したがって、各合併するため、我々は、追加記録のサイズを設定する必要があり、及びモバイルのセット・サイズがサイズのセットに追加され、追加(DIS [ルート_ {\ \テキスト{ モバイルの}}集合は] \)であるべきです\(size_ \テキスト{セットは} \添加しました)
コードの一部として合成
rg int x=read(),y=read(),xx=find(x),yy=find(y);
fa[xx]=yy;
dis[xx]=size[yy];
size[yy]+=size[xx];
- 回答処理
この問題では問い合わせが必要です
「ヤンWeili最初の\(I \) USS最初の\(J \)同じ列に、軍艦の数は、それらの間に配置された場合、同じ列に、現在持っているかどうかUSS。」
だから、\(ANS = \始める{例 } -1&I \テキスト{ と} J \テキスト{インナーのないセット} \\ | DIS [i]は-dis [J] | -1&I \テキスト{ と} J \テキスト{セット} \端{ケース} \)で
ACコード:
#include<bits/stdc++.h>
using namespace std;
#define rg register
#define ll long long
#define ull unsigned long long
namespace Enterprise{
inline int read(){
rg int s=0,f=0;
rg char ch=getchar();
while(not isdigit(ch)) f|=(ch=='-'),ch=getchar();
while(isdigit(ch)) s=(s<<1)+(s<<3)+(ch^48),ch=getchar();
return f?-s:s;
}
const int N=30015;
int fa[N],dis[N],size[N],t;
inline int _abs(int x) { return x>=0?x:-x; }
inline int find(int x){
if( fa[x]==x ) return x;
int root=find(fa[x]);
dis[x]+=dis[fa[x]];
return fa[x]=root;
}
inline void main(){
for(rg int i=1;i<=30000;i++) fa[i]=i,size[i]=1;
t=read();
for(rg int i=1;i<=t;i++){
char opt=getchar();
while(!isalpha(opt)) opt=getchar();
if(opt=='C'){
rg int x=read(),y=read(),xx=find(x),yy=find(y);
if(xx==yy) printf("%d\n",abs(dis[x]-dis[y])-1);
else printf("-1\n");
}else{
rg int x=read(),y=read(),xx=find(x),yy=find(y);
fa[xx]=yy;
dis[xx]=size[yy];
size[yy]+=size[xx];
}
}
}
}
signed main(){
Enterprise::main();
return 0;
}
\(5 \)最後に書かれました
非常に良いデータ構造として互いに素セット、いくつかの互いに素セットの基本的な動作を説明するために、このブログに名前を付けるために、多くのがあり、そのアプリケーションを展開します。最後に、まだ努力する必要があり、上に来ます!