ディレクトリ
I、ばらばらのセットで
1.定義
互いに素なセット(互いに素-セット)動的に非重複の複数のセットを維持し、サポートしている組み合わせでクエリ二つの動作のデータ構造。
2.基本操作
1.複合(ユニオン/マージの)1:二組合わせ。
2.クエリ(検索/取得):クエリ要素がセットに属します。
実際の動作では、我々はそれがルート要素(父として理解することができる)であり、全体のコレクションを表すために点を使用します。
3.実現
我々は、アレイを作成fa[ ]
またはpre[ ]
互いに素セットを表し、fa[i]
表すi
親ノード。
初期化:すべての点が集まりなので、親が自分でfa[i]=i
問い合わせ:各ノードは、常にその親を探して、この時点での親ノードがルートノードのために独自の、その後、収集ポイントであれば、返品そのポイント。
レビュー:すなわち、二組の合成ルートを2つだけセットをマージはfa[RootA]=RootB
、ここでRootA
、RootB
二つの要素のルートです。
パス圧縮:
実際には、我々は唯一のクエリプロセスのルートノードは、(質問の一部を除いて)このツリー形式に関係していないものを気に。したがって、我々は、各時点を照会することができ、単一の操作の複雑さをルーツに、すべてのポイントを訪問し、その方法はパス圧縮と呼ばれている\(O(logN個)\) 。
図良好消費(写真の状態圧縮工程)と一緒に:
第二に、コードの実装
初期化テンプレート:
for(int i=1;i<=n;i++) pre[i]=i;
(パス圧縮付き)テンプレートクエリ:
int Find(int x){
if(x==pre[x]) return x;
return pre[x]=Find(pre[x]);
}
テンプレートをマージ:
void merge(int x,int y){
int fx=Find(x),fy=Find(y);
if(fx!=fy) pre[fx]=fy;
}
//主函数内
merge(a,b);
第三に、例のいくつか
例1:P1551の親戚
テンプレートのタイトル。ここでは保留コードに...
:それはまた、テンプレートの質問であるP2814の系譜、推奨マップ(STL)。
例2:P1536村
中国聯通をビルドするためにどのように多くの道路のすべての方法を販売することを求めて。
道路の私たち最初のポイントは、合併を構築してきた、それが道路を構築するために必要とされる場合は、すべての集合の点の数は1より大きくなければなりません。
限り、我々は自分自身に親ノードかどうかを尋ねるのポイントでコレクションを形成するために、単一のポイントかどうかを調べるよう。
最終的に、我々は出力最終的な答えを失うこと自体に設定されたルートノードの親ノードにマージことは注目に値します。
コード:
#include <bits/stdc++.h>
using namespace std;
int pre[1000001],n,m,ans;
inline int Find(int x){
return pre[x]==x?x:pre[x]=Find(pre[x]);
}
inline void Union(int x, int y){
int fx=Find(x),fy=Find(y);
if(fx!=fy) pre[fx]=fy;
}
int main()
{
while(scanf("%d",&n)&&n){
ans=0;
scanf("%d", &m);
for(int i=1;i<=n;i++) pre[i]=i;
for(int i=1,x,y;i<=m;i++){
scanf("%d%d",&x,&y);
Union(x,y);
}
for(int i=1;i<=n;i++){
if(Find(i)==i) ans++;
}
printf("%d\n",ans-1);
}
return 0;
}
例3:P1396救助
FBI!UP開きます!参照してください最大最小求めて、我々は半分の解決を使用します知っています。
進むべき道に沿って、我々は、実現可能である渋滞の程度を決定する際に、すべての混雑したミッド側面が削除されるよりも大きな混雑の半分程度であることができ、最終的に判断互いに素セットs
ポイントとt
ポイントがユニコムにあります。
コード:
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define N 50050
using namespace std;
int l=INF,r=-1,n,m,s,t,ans;
int pre[N],x[N],y[N],cost[N];
inline int find(int x){return x==pre[x]?x:pre[x]=find(pre[x]);}
inline int check(int mid){
for(int i=1;i<=n;i++) pre[i]=i;
for(int i=1;i<=m;i++){
if(cost[i]>mid) continue;
int fx=find(x[i]),fy=find(y[i]);
if(fx!=fy) pre[fx]=fy;
}
if(find(s)==find(t)) return 1;
return 0;
}
int main()
{
scanf("%d%d%d%d",&n,&m,&s,&t);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&x[i],&y[i],&cost[i]);
l=min(l,cost[i]);r=max(r,cost[i]);
}
while(l<=r){
int mid=(l+r)>>1;
if(check(mid)){
r=mid-1;
ans=mid;
}
else l=mid+1;
}
printf("%d",ans);
return 0;
}
例4:P1621セット
すべての素因数pの数が複合互いに素セットを求めて、最終的に解決されるよりも大きくなります。
#include<bits/stdc++.h>
#define N 100010
using namespace std;
int a,b,cmp,ans,cnt;
int pre[N],notp[N],p[N];
int Find(int x){
if(x==pre[x]) return x;
return pre[x]=Find(pre[x]);
}
inline int Union(int x,int y){
int fx=Find(x),fy=Find(y);
if(fx!=fy) pre[fx]=fy;
}
inline void E_prime(){
notp[1]=1;
for(int i=2;i<=b;i++){
if(notp[i]) continue;
for(int j=i*2;j<=b;j+=i){
notp[j]=1;
}
}
for(int i=cmp;i<=b;i++)
if(not notp[i]) p[++cnt]=i;
}
int main()
{
scanf("%d%d%d",&a,&b,&cmp);
for(int i=a;i<=b;i++) pre[i]=i;
E_prime();
for(int i=1;i<=cnt;i++)
for(int j=2;j*p[i]<=b;j++)
if(j*p[i]>=a&&(j-1)*p[i]>=a)
Union(p[i]*j,p[i]*(j-1));
for(int i=a;i<=b;i++)
if(pre[i]==i) ans++;
printf("%d",ans);
return 0;
}
例5:P4185 [USACO18JAN] MooTube
Kと小さなソートの大から右側には、このような大規模なKは、検索の重複を避けるために、小さなKを満たしています。
コード:
#include<bits/stdc++.h>
#define N 300010
using namespace std;
int n,q,fa[N],size[N],ans[N];
inline int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
inline void Union(int x,int y){
int fx=find(x),fy=find(y);
if(fx==fy) return;
size[fx]+=size[fy],fa[fy]=fx;
}
struct edge{
int u,v,r;
}p[N];
struct node{
int k,v,id;
}ask[N];
inline int CMP(edge a,edge b){return a.r>b.r;}
inline int cmp(node a,node b){return a.k>b.k;}
int main()
{
scanf("%d%d",&n,&q);
for(int i=1;i<n;i++)
scanf("%d%d%d",&p[i].u,&p[i].v,&p[i].r);
for(int i=1;i<=q;i++)
scanf("%d%d",&ask[i].k,&ask[i].v),ask[i].id=i;
for(int i=1;i<=n;i++){fa[i]=i;size[i]=1;}
sort(p+1,p+n,CMP);
sort(ask+1,ask+q+1,cmp);
int pos=1;
for(int i=1;i<=q;i++){
while(pos<n&&p[pos].r>=ask[i].k){
Union(p[pos].u,p[pos].v);
pos++;
}
ans[ask[i].id]=size[find(ask[i].v)]-1;
}
for(int i=1;i<=q;i++)
printf("%d\n",ans[i]);
return 0;
}
例6:P1197 [JSOI2008]スター・ウォーズ
数字に対する各通信ブロックの後に尋ねた質問の数を取得します。互いに素セットは、操作をマージすることができますが、操作を分離することはできませんので、我々はプロセスを逆転します。ユニコム通信を指すように吹き付け、そして合わせ、その時ブロックに通信ポイントの数をカウントする吹き続け、続いて、この時点で数ブロックをカウントすることができません。
コード:
#include<bits/stdc++.h>
#define N 400010
using namespace std;
int n,m,k,pre[N],first[N],nxt[N],go[N];
int poi[N],off[N],tot,from[N],sum,ans[N];
inline void add_edge(int u,int v){
nxt[++tot]=first[u];
first[u]=tot;
go[tot]=v;
from[tot]=u;
}
inline int Find(int x){
return x==pre[x]?x:pre[x]=Find(pre[x]);
}
inline void Union(int x,int y){
int fx=Find(x),fy=Find(y);
if(fx!=fy) pre[fx]=fy;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) pre[i]=i;
for(int i=1,x,y;i<=m;i++){
scanf("%d%d",&x,&y);
add_edge(x,y);
add_edge(y,x);
}
scanf("%d",&k);
for(int i=1;i<=k;i++){
scanf("%d",&poi[i]);
off[poi[i]]=1;//标记
}
sum=n-k;//剩余的点
for(int i=1;i<=m<<1;i++)//双向边
if(!off[from[i]]&&!off[go[i]])//两个点都没被炸
if(Find(from[i])!=Find(go[i]))
sum--,Union(from[i],go[i]);//合并,更新连通块个数
ans[k+1]=sum;//最后一个答案
for(int i=k;i>=1;i--){
sum++;//恢复这个点后自己也算一个连通块,因此个数要+1
off[poi[i]]=0;//恢复
for(int e=first[poi[i]];e;e=nxt[e]){
int v=go[e];
if(!off[v]&&Find(poi[i])!=Find(v)){
Union(poi[i],v);//合并
sum--;
}
}
ans[i]=sum;
}
for(int i=1;i<=k+1;i++)
printf("%d\n",ans[i]);
return 0;
}
:7例は狂ったパンをbzoj2054
我々は最終的にだけ彼の最後の色に感染しているに感染した色の完全な頭部を発見したので、我々は後方ハンドル、我々はすでに染めパンが染色されることはありません維持する必要があります。この時点で、彼の父この点を見つけるために、右の走査範囲に左からまだ(親ノードが区間にまだある場合)は、ノードの色を塗りつぶしていない父は、私たちがdyedこの点を入れて、これは次のマーカーを指しますノード。シンプルな用語は、我々は汚れを指すようにしたい場合は、この点は染色に別のポイントには染色を私たちにもたらしていないということです。
#include<bits/stdc++.h>//每次扫描的元素被染色后指向其没有被染色的馒头
#define N 1000010 //路径压缩以节省时间
#define ll long long
using namespace std;
int n,m,p,q;
int pre[N],ans[N];
inline int Find(int x){
return (pre[x]==x)? x:pre[x]=Find(pre[x]);
}
inline void Dye_mantou(ll l,ll r,int color)
{
for(int i=Find(l);i<=r;i=Find(i)){ //方向指向数组末尾
ans[i]=color;
pre[i]=i+1;
}
}
int main()
{
scanf("%d%d%d%d",&n,&m,&p,&q);
for(int i=1;i<=n+1;i++) pre[i]=i;
for(int i=m;i>=1;i--){
ll l=(i*p+q)%n+1;
ll r=(i*q+p)%n+1;
if(l>r) swap(l,r);
Dye_mantou(l,r,i);
}
for(int i=1;i<=n;i++)
printf("%d\n",ans[i]);
return 0;
}
例8:P2294 [HNOI2005]スマートビジネスマン
加重互いに素セット。1月と互いに素設定ビルド関係によって。
コード:
#include<bits/stdc++.h>
#define N 100010
using namespace std;
int n,m,w;
int pre[N],d[N];
int Find(int x){
if(pre[x]==x) return x;
int temp=Find(pre[x]);
d[x]+=d[pre[x]];
return pre[x]=temp;
}
int Union(int x,int y,int v)
{
int fx=Find(x);
int fy=Find(y);
if(fx==fy)
return d[x]-d[y]==v;
if(fx<fy){
pre[fx]=fy;
d[fx]=d[y]+v-d[x];
return 1;
}
else{
pre[fy]=fx;
d[fy]=d[x]-v-d[y];
return 1;
}
}
int main()
{
scanf("%d",&w);
while(w--)
{
scanf("%d%d",&n,&m);
int flag=1;
memset(d,0,sizeof(d));
for(int i=0;i<=n;i++) pre[i]=i;
for(int i=1,s,t,v;i<=m;i++){
scanf("%d%d%d",&s,&t,&v);
if(flag==0) continue;
if(!Union(s-1,t,v)) flag=0;//包含s月,所以往前一个月为s-1
}
if(flag) printf("true\n");
else printf("false\n");
}
return 0;
}
実施例9:P1892 [BOI2003]グループ
2人の関係の主題は、私たちが二人の友人との関係をマージします、それは非常に明確にしました。2人の敵との関係については、敵の敵のためには、私の友人であるので、我々は独自の仮想敵を構築し、お互いに友情を形成することができます。
コード:
#include<bits/stdc++.h>
#define N 6000
using namespace std;
int n,m,ans,pre[N];
char ch;
int Find(int x){
return (x==pre[x])? x:pre[x]=Find(pre[x]);
}
inline void Union(int x,int y){
int fx=Find(x);
int fy=Find(y);
pre[fx]=fy;
return;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=2*n;i++) pre[i]=i;
for(int i=1,u,v;i<=m;i++){
cin>>ch>>u>>v;
if(ch=='F') Union(u,v);
if(ch=='E'){
pre[Find(u+n)]=Find(v);
pre[Find(v+n)]=Find(u);
}
}
for(int i=1;i<=n;i++)
if(pre[i]==i) ans++;
printf("%d",ans);
return 0;
}
名前のどの問題ではありません。↩