事前知識
永続配列
簡単な紹介
テンプレートへの最初の質問:
大雑把にあなたのデータ構造を記述することを意味し、サポート
-
どこのセット、Bのマージ
-
k個の操作に戻る後の状態
-
発見とbが同じコレクションの内部に含まれていません
これは、第2の動作よりも取り除か共通互いに素セットを解決することができる見ることができます
共通の互いに素なセットは、一般に、アレイに基づいているが、耐久性の配列に基づいている互いに素セットを保持することができます
(実際に達成するのは非常に難しいことではありません実際には、非常に高いこの事音の名前を言って
互いに素セット
私は普通の互いに素セットを書きますと信じています。。。しかし、もっと重要な一つのことがあります
最も一般的で簡単な最適化のチェックは2互いに素設定おおよその時間の複雑さが一定になることができるが長続きするように最適化され、ランクとパス圧縮合併(私は一般的に唯一のパス圧縮を果たしているより多くの野菜をしています)によって設定されています互いに素セットパスのことで圧縮することはできません。以来パス圧縮プロセスもOK FAアレイ、改変された一般の配列を改変するが、それぞれの変化がチェーンを追加する永続配列であるであろう。私たちは主に、ランクに応じて合併のアイデアを使用して、時間が経つにつれて、全探索空間と複雑さは、オフに設定されます。
実際には、ランクの合併によると、それは難しいことではありません、ここでは、ランクに応じてチェックし、マージコードの共通セットです。
void merge(int x,int y) {
x=find(x);
y=find(y);
if(x==y) return ;
if(dep[x]<dep[y])
fa[x]=y;
else {
fa[y]=x;
if(dep[x]==dep[y]) dep[x]++;
}
}
合併時に小さいから合併の大きい深さ、同じ深さだけ奥行きDEPであり、次いで、[X] ++後特別な文章上のような、あなたは最大樹高が存在しないログ(n)は、であることを確認することができチェーン。
具体的な原則。。。自分の百度で、とにかく、それは難しいことではありません(のようなヒューリスティックマージビット?)
永続互いに素セット
私たちの最適化は、合併のランクに基づいており、我々は二つの配列を維持する必要がありますので、永続的な(faとDEP)することができるからです。もちろん、それは二つの配列であることから、我々は多くのメモリ空間として二回開かれます:
struct node {
int l,r,sum;
} t[maxn*40*2];
その後、我々は、これは、開口部2永続的な配列と等価である異なる根の配列の配列でマーク2つのルートを開設してからメモリに加えてカウンターを割り当てるために使用し、トピックのいくつかは残り何
int n,m,tot,cnt,rootfa[maxn],rootdep[maxn];
次に、所与の配列代入FAの動作と互いに素なセット開始は、FA [I] = I、我々は永続番号を書き込むことができるように、完全なこの作業を構築するために機能します
void build(int l,int r,int &now) {
now=++cnt;
if(l==r) {
t[now].sum=++tot;
return;
}
int mid=(l+r)/2;
build(l,mid,t[now].l);
build(mid+1,r,t[now].r);
}
トットここで、上記で定義され、リーフノードをインクリメントするために使用され、それはまだ非常に簡単でなければなりません
その後、配列ではなく、ここではそれらを繰り返すのボードの持続性があります:
void modify(int l,int r,int ver,int &now,int pos,int num) {//ver指向历史版本,now指向当前节点
t[now=++cnt]=t[ver];
if(l==r) {
t[now].sum=num;
return;
}
int mid=(l+r)/2;
if(pos<=mid) modify(l,mid,t[ver].l,t[now].l,pos,num);
else modify(mid+1,r,t[ver].r,t[now].r,pos,num);
}
int query(int l,int r,int now,int pos) {
if(l==r) return t[now].sum;
int mid=(l+r)/2;
if(pos<=mid) return query(l,mid,t[now].l,pos);
else return query(mid+1,r,t[now].r,pos);
}
その後、我々は関数を記述するために見つけます。
機能自体は比較的簡単です見つけるが、私たちは圧縮パスをしないように注意してください。それは直接コードを配置した場所理由は、以前の話されています:
int find(int ver,int x) {
int fx=query(1,n,rootfa[ver],x);
return fx==x?x:find(ver,fx);
}
その後、我々は2の操作を達成するためにどのようにのを見てみましょう、何の機能をマージするために急いで言っていません。
2操作がk番目のバージョンに戻され、我々はルート配列を持っている場合は、当然のことながら、我々は書くことができます。
rootfa[ver]=rootfa[x];
rootdep[ver]=rootdep[x];
、コピーされ、全バージョンに相当します会長同じツリーへのすべてのポイント彼らはので、現在のバージョンへのポイントのver、我々は単に、根のxバージョンの値に直接コピーすることができます。
そして、マージ機能は、マージ機能は、一般のランクに応じて、実際には難しいことではないようなランダムな変化を介して結合コードとおりにしました。
void merge(int ver,int x,int y) {
x=find(ver-1,x);
y=find(ver-1,y);
if(x==y) {
rootfa[ver]=rootfa[ver-1];
rootdep[ver]=rootdep[ver-1];
} else {
int depx=query(1,n,rootdep[ver-1],x);
int depy=query(1,n,rootdep[ver-1],y);
if(depx<depy) {
modify(1,n,rootfa[ver-1],rootfa[ver],x,y);
rootdep[ver]=rootdep[ver-1];
} else if(depx>depy) {
modify(1,n,rootfa[ver-1],rootfa[ver],y,x);
rootdep[ver]=rootdep[ver-1];
} else {
modify(1,n,rootfa[ver-1],rootfa[ver],x,y);
modify(1,n,rootdep[ver-1],rootdep[ver],y,depy+1);
}
}
}
質問に、なぜ版-1、我々はマージにプログラムを実行する前に、あなたがして、そこにマージにブレークポイントをヒットとして文言個人コード(内部の他の多くのdalaoボードではない問題)の問題、(理解することができるので、一時停止で停電時のプログラムの状態)、この時点で新しいバージョンとポイントのverありませんが、このバージョンでは何が、我々は、関数が必要とされているマージして[うた-1]ルートです尖ったバージョンそれは修正する点や、クエリの過去のバージョンは(私たちは、クエリを変更するか、rootアレイの後ろに最後の2を返されるかどうか、とにかく)のver-1でなければなりませんたびに内部のマージ機能に非常に注意を払う、内の値。あなたはその後、すべてのDEPに注意を払うにある場合、それは、その後、同期の変更がありませんしている場合は、逆に、内部に行くためにバージョン版DEPに同期させるための配列の不可欠版-1バージョンがあるとき、配列には影響を与えません。
同様に、我々は)関数はうた-1に行ったことがあるだろうが、クエリは、我々が主な機能の背面に直接見つけるそうする前に、配列に変更を伴わないので、:(内側に書かれているACコードを見つけます
rootfa[ver]=rootfa[ver-1];
rootdep[ver]=rootdep[ver-1];
あなたは、なぜもそうマージそれをしないで従事ので見つけることができますので、今、あなたは、疑問を持っていること?
だから、我々はすでに声明今+ 1を与えたの最初の行で見つけることができ、アレイ内で持続することがテンプレートに戻り、今ここに渡すか、参照。我々は、マージ前に2つの単語を記述する場合、我々は手動版-1与えることではなく、全体のルートのコピーを持っているので、我々は最終的に変更は、間違いなく間違っている、[2 +うた] rootfaです
以下の質問は、テンプレートACコードを与えられます。
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
struct node {
int l,r,sum;
} t[maxn*40*2];
int n,m,tot,cnt,rootfa[maxn],rootdep[maxn];
void build(int l,int r,int &now) {
now=++cnt;
if(l==r) {
t[now].sum=++tot;
return;
}
int mid=(l+r)/2;
build(l,mid,t[now].l);
build(mid+1,r,t[now].r);
}
void modify(int l,int r,int ver,int &now,int pos,int num) {
t[now=++cnt]=t[ver];
if(l==r) {
t[now].sum=num;
return;
}
int mid=(l+r)/2;
if(pos<=mid) modify(l,mid,t[ver].l,t[now].l,pos,num);
else modify(mid+1,r,t[ver].r,t[now].r,pos,num);
}
int query(int l,int r,int now,int pos) {
if(l==r) return t[now].sum;
int mid=(l+r)/2;
if(pos<=mid) return query(l,mid,t[now].l,pos);
else return query(mid+1,r,t[now].r,pos);
}
int find(int ver,int x) {
int fx=query(1,n,rootfa[ver],x);
return fx==x?x:find(ver,fx);
}
void merge(int ver,int x,int y) {
x=find(ver-1,x);
y=find(ver-1,y);
if(x==y) {
rootfa[ver]=rootfa[ver-1];
rootdep[ver]=rootdep[ver-1];
} else {
int depx=query(1,n,rootdep[ver-1],x);
int depy=query(1,n,rootdep[ver-1],y);
if(depx<depy) {
modify(1,n,rootfa[ver-1],rootfa[ver],x,y);
rootdep[ver]=rootdep[ver-1];
} else if(depx>depy) {
modify(1,n,rootfa[ver-1],rootfa[ver],y,x);
rootdep[ver]=rootdep[ver-1];
} else {
modify(1,n,rootfa[ver-1],rootfa[ver],x,y);
modify(1,n,rootdep[ver-1],rootdep[ver],y,depy+1);
}
}
}
int main(void) {
scanf("%d %d",&n,&m);
build(1,n,rootfa[0]);
for(int ver=1; ver<=m; ver++) {
int opt,x,y;
scanf("%d",&opt);
if(opt==1) {
scanf("%d %d",&x,&y);
merge(ver,x,y);
} else if(opt==2) {
scanf("%d",&x);
rootfa[ver]=rootfa[x];
rootdep[ver]=rootdep[x];
} else {
scanf("%d %d",&x,&y);
rootfa[ver]=rootfa[ver-1];
rootdep[ver]=rootdep[ver-1];
int fx=find(ver,x),fy=find(ver,y);
printf("%d\n",fx==fy?1:0);
}
}
}
ところで、あなたは、プロパティのコレクションを維持したい場合(例えば、大きさの収集や?など)、私たちはばらばらセットを使用する権利を持つ永続的でなければなりません
実際には、非常に単純な、それは維持するために、新しい変数を開くために、アレイ内で存続することができます