【Codeforcesの603e]田園倒錯変態(LCT)
フェイス質問
図は、n個の点、順次無向図面の右側に追加m個のバンドが存在する単離されました。各エッジのセットポイントが奇数であることを、図度を最大重み値設定エッジの重みとして定義され、正当なものです。エッジの各添加後、右呼び掛け最小法定重量エッジ設定値、法定の出力側のセットは、-1が存在しません。
\(N \当量10 ^ 5、M \ 3当量\回10 ^ 5 \)
分析
利用可能なハンドプレイサンプル:有効エッジ集合の存在下、各ブロックのサイズが偶数の通信である場合にのみ
証明:
充足を証明する:正当な通信ブロックサイズが奇数であると仮定し、そのブロックは、総度の奇数です。しかし、全ての点と偶数のある程度は(片面2点度カウントされるため)。だから、セットの法的側面は存在しません。したがって、通信ブロックサイズは偶数でなければなりません。
そして、正当化:任意のスパニングツリー通信ブロックを得ました。各非ルートノードのボトムアッププロセス(X \)\。もし\(X \)そのスパニングツリー子側に、選出されたエッジの組の偶数を有します。次に\(X \)に\(FA(X)\)ように、エッジのセット側に追加される(X \)\次数が奇数です。ルートノードのために、我々は、非ルートノード度が奇数のすべてのセットが、すべての点度の数であり、偶数入れているという理由だけで、その程度の根は奇数でなければなりません。
十分な注意事項の証明は、回答コンストラクタを与えられています。この方法は、元の通信ブロックにまたがるエッジを選択しなければなりません。そして、元のブロックを通信する小さなブロックの通信に分割される(場合(X \)\ではない接続に)\)\(FA(X)、各ブロックの最小スパニングツリーが正当な新しいユニコムの最小量でありますサイドセット。
我々は小から大まで重量で、森に各エッジを維持するために、プライオリティキューを使用しています。実現のためにpriority_queue
いくつかの制限の、我々は切り替えset< pair<int,int> >
。最初のキーを右側に、キーワードIDの第2の面を
その後、我々は、LCTは、スパニングツリーの森を維持することができます。
各縁取り作業を考えてみましょう\((U、V、W )\) 効果。
場合(Uは、V \)\接続されていた、そして私たちは、探し最小全域木を維持する\(U \)への\(V \)パスの最大の右側にある\(W 0 \) 。
場合(\ W> 0 W)\、その後、スパニングツリーの上に置く(W_0 \)\に(\ w)が\。同時に更新
set
。、エッジの重みの最大出力可解集合(度の奇数がある場合ユニコムブロック)そうでなければ、現在の解決できるかどうかを決定します。
場合(U、V \)\通信していない、我々は新しいエッジとLCTを追加しました
set
。ユニコムと奇数ブロック度の数を更新します。スパニングツリーの変更のエッジは、私たちが必要な場合は、最適な答えを再計算します。
set
最大のエッジスタート、このエッジの除去かどうかを確認します。私たちは、チェックを削除することができた場合は削除され、二番目に大きい、それ以外の場合は終了されます。
そして、基本的な操作に対する当社のLCTの追加は、サポートします:
最大の側の上にクエリパス右。同様に[ BZOJ2594] [WC2006]秘書水(クラスカル+ LCT)を検討する点として。
クエリサブツリースパニングツリー上の点の大きさ。同様【BJOI2014]大統合(リンクカットツリー)仮想サブツリー情報のメンテナンスをすることができます。私たちがポイントになることを見ているので注意が、ポイント(P \)\サブツリーは、LCTになりました\( - 1)= 2P-1 \ P-+(P) 。その後、実際の点はである\((SZ [P] +1 )/ 2 \)
複雑分析:操作のLCT複雑である\(O((N-M +)\ Nログ)\) 。各エッジは、一度だけセット内の複雑度を除去する(O(M \ログM)\)\。
概要:これは、スパニングツリーの問題への問題の性質の素晴らしいタイトル最初に使用され、その後、LCTは、スパニングツリーを維持し、両方のサブルーチンに関する情報を保持し兼ね備えています。難しいの両方のデータ構造の難易思考を持っています。人々は、タイトルの親指を与えています!
コード
#include<iostream>
#include<cstdio>
#include<cstring>
#include<set>
#include<utility>
#define maxn 100000
#define maxm 300000
using namespace std;
int n,m;
struct edge {
int from;
int to;
int len;
} E[maxm+5];
struct LCT {
#define fa(x) (tree[x].fa)
#define lson(x) (tree[x].ch[0])
#define rson(x) (tree[x].ch[1])
struct node {
int ch[2];
int fa;
int sz;//原树上的子树大小
int vsz;//虚子树大小
int id;
int maxid;
int revm;
} tree[maxn+maxm+5];
inline bool is_root(int x) {
return !(lson(fa(x))==x||rson(fa(x))==x);
}
inline int check(int x) {
return rson(fa(x))==x;
}
void push_up(int x) {
tree[x].sz=tree[lson(x)].sz+1+tree[rson(x)].sz+tree[x].vsz;
tree[x].maxid=tree[x].id;
if(E[tree[lson(x)].maxid].len>E[tree[x].maxid].len) tree[x].maxid=tree[lson(x)].maxid;
if(E[tree[rson(x)].maxid].len>E[tree[x].maxid].len) tree[x].maxid=tree[rson(x)].maxid;
}
void reverse(int x) {
swap(lson(x),rson(x));
tree[x].revm^=1;
}
void push_down(int x) {
if(tree[x].revm) {
reverse(lson(x));
reverse(rson(x));
tree[x].revm=0;
}
}
void push_down_all(int x) {
if(!is_root(x)) push_down_all(fa(x));
push_down(x);
}
void rotate(int x) {
int y=fa(x),z=fa(y),k=check(x),w=tree[x].ch[k^1];
tree[y].ch[k]=w;
tree[w].fa=y;
if(!is_root(y)) tree[z].ch[check(y)]=x;
tree[x].fa=z;
tree[x].ch[k^1]=y;
tree[y].fa=x;
push_up(y);
push_up(x);
}
void splay(int x) {
push_down_all(x);
while(!is_root(x)) {
int y=fa(x);
if(!is_root(y)) {
if(check(x)==check(y)) rotate(y);
else rotate(x);
}
rotate(x);
}
push_up(x);
}
void access(int x) {
for(int y=0; x; y=x,x=fa(x)) {
splay(x);
tree[x].vsz-=tree[y].sz-tree[rson(x)].sz;//把原来是虚子树,现在是实的减掉
rson(x)=y;
push_up(x);
}
}
void make_root(int x) {
access(x);
splay(x);
reverse(x);
}
void split(int x,int y) {
make_root(x);
access(y);
splay(y);
}
void link(int x,int y) {
make_root(x);
fa(x)=y;
tree[y].vsz+=tree[x].sz;
push_up(y);
}
void cut(int x,int y) {
split(x,y);
fa(x)=lson(y)=0;
push_up(y);
}
void add_edge(int id) {
link(E[id].from,id+n);
link(E[id].to,id+n);
}
void del_edge(int id) {
cut(E[id].from,id+n);
cut(E[id].to,id+n);
tree[id+n].vsz=0;
}
int find_root(int x) {
access(x);
splay(x);
while(lson(x)) x=lson(x);
return x;
}
int query_route(int x,int y) {
split(x,y);
return tree[y].maxid;
}
} T;
set< pair<int,int> >ed;//按边权从小到大存储生成树中的边,first为边权,second为编号
inline int is_odd(int x) {
//判断x所在联通块的点数是否为奇数
//LCT里p个点,p-1条边,sz[x]=2p-1
//真正的点数为(sz[x]+1)/2
return ((T.tree[x].sz+1)/2)%2==1;
}
void build() {
while(1) {
int id=(--ed.end())->second;
int x=E[id].from;
int y=E[id].to;
T.split(x,y);
if(is_odd(x)) break;
T.del_edge(id);
ed.erase(*(--ed.end()));
}
}
int main() {
scanf("%d %d",&n,&m);
for(int i=1; i<=n+m; i++) {
if(i<=n) T.tree[i].id=0;
else T.tree[i].id=i-n;
T.push_up(i);
}
for(int i=1; i<=m; i++) scanf("%d %d %d",&E[i].from,&E[i].to,&E[i].len);
int odd_sz_cnt=n;
for(int i=1; i<=m; i++) {
int u=E[i].from,v=E[i].to,w=E[i].len;
if(T.find_root(u)==T.find_root(v)) {
int now=T.query_route(u,v);
if(w<E[now].len) {
T.del_edge(now);
T.add_edge(i);
ed.erase(make_pair(E[now].len,now));
} else {
if(odd_sz_cnt>1) printf("-1\n");
else printf("%d\n",(--ed.end())->first);//输出最大边
continue;
}
} else {
T.make_root(u);
odd_sz_cnt-=is_odd(u);
T.make_root(v);
odd_sz_cnt-=is_odd(v);
//把原来的两个联通块的贡献减掉
T.add_edge(i);
T.make_root(u);
odd_sz_cnt+=is_odd(u);
//再加回新的贡献
}
ed.insert(make_pair(w,i));
if(odd_sz_cnt>0) {
printf("-1\n");
continue;
}
build();//构造出联通块里选择的边集
printf("%d\n",(--ed.end())->first);
}
}