【Codeforces 603E】Pastoral Oddities
LCT
题目大意:
给定一张无向图,边带权。依次加入 $m$ 条边。
每次加入一条边后,询问:
能否选出一个边的集合,使得每个点的度数都是奇数。能则输出这样的边集中,边权最大的边最小是多少。不能输出 $-1$。
题解:
有一个结论:
对一个大小为 $n$ 的连通块,若 $n$ 是偶数,则这个连通块中一定存在一种选边方案,使得连通块内每个点的度数都是奇数。反之则一定没有。
当 $n$ 是奇数时,由于总的度数是偶数,所以不可能每个点的度数是奇数。
当 $n$ 是偶数时,可以这样构造:
- 找一棵生成树,并选定一个根。
- 自底向上,对每个结点,考虑其儿子,如果其一个儿子的度数为偶数,则连上它与儿子之间的边。
这里当做到根时,由于存在奇数个度数为奇数的结点,所以根的度数也一定是奇数。
考虑什么情况下,大小为奇数的连通块会减少。
只有当两个奇数连通块连边时,这样的连通块个数才会减少 $2$。
考虑用 LCT 维护最小生成树,同时记录奇数连通块个数,并用一个 set
动态记录当前用了哪些边。
每次加入边的时候,如果两个点已经连通,则观察新加入的边能否替换最小生成树中的边,能则替换之。不改变奇偶性。
如果不连通,则连通之,并修改奇数连通块个数。
LCT 上需要维护链上边权最大值以及子树大小(奇偶性)。
执行完这些操作后,如果奇数连通块个数不为 $0$ 则输出 $-1$。
否则,我们考虑依次观察边权最大的边,看看是否能删掉。这里边已经用 set
排好序了。
如果删掉仍然满足条件,则删掉,否则不删,并输出最大边长。
正确性在于,一旦答案不为 $-1$,之后无论如何怎么加边,都不可能产生奇数连通块。故可以在满足条件的情况下随便删已有的边。
时间复杂度 $O(m\log m)$。
Code:
#include<iostream>
#include<algorithm>
#include<utility>
#include<set>
using namespace std;
const int N=4e5+5;
int n,m;
struct edge{
int u,v,w;
}e[N];
namespace lct{
int fa[N],ch[N][2],sz[N],val[N],sv[N],s_[N];
pair<int,int>mx[N],dis[N];
bool tag[N];
inline bool ckr(int x,int p=1){return ch[fa[x]][p]==x;}
inline bool isroot(int x){return!(ckr(x)||ckr(x,0));}
inline void flip(int x){tag[x]^=1,swap(ch[x][0],ch[x][1]);}
inline void update(int x){
sz[x]=sz[ch[x][0]]+sz[ch[x][1]]+1;
sv[x]=sv[ch[x][0]]+sv[ch[x][1]]+s_[x]+val[x];
mx[x]=max({dis[x],mx[ch[x][0]],mx[ch[x][1]]});
}
inline void pushdown(int x){
if(tag[x]){
if(ch[x][0])flip(ch[x][0]);
if(ch[x][1])flip(ch[x][1]);
tag[x]=0;
}
}
inline void rotate(int x){
int y=fa[x],z=fa[y],k=ckr(x);
if(!isroot(y))ch[z][ckr(y)]=x;
fa[y]=x,fa[x]=z,fa[ch[x][!k]]=y;
ch[y][k]=ch[x][!k],ch[x][!k]=y,update(y);
}
inline void splay(int x){
static int sta[N],top;sta[top=1]=x;
for(int y=x;!isroot(y);sta[++top]=y=fa[y]);
while(top)pushdown(sta[top--]);
for(;!isroot(x);rotate(x))
if(!isroot(fa[x]))rotate((ckr(x)^ckr(fa[x]))?x:fa[x]);
update(x);
}
inline void access(int x){
for(int t=0;x;ch[x][1]=t,t=x,x=fa[x])splay(x),s_[x]+=sv[ch[x][1]]-sv[t];
}
inline void makeroot(int x){access(x),splay(x),flip(x);}
inline void split(int x,int y){makeroot(x),access(y),splay(y);}
inline int findroot(int x){
for(access(x),splay(x);ch[x][0];x=ch[x][0]);
splay(x);return x;
}
inline bool connect(int x,int y){return findroot(x)==findroot(y);}
inline void link(int x,int y){makeroot(x);if(findroot(y)!=x)makeroot(y),fa[x]=y,s_[y]+=sv[x];}
inline void cut(int x,int y){split(x,y);if(ch[y][0]==x&&sz[x]==1)ch[y][0]=fa[x]=0,update(y);}
pair<int,int>ask(int x,int y){
split(x,y);
return mx[y];
}
int get(int x){makeroot(x);return sv[x];}
}
set<pair<int,int> >st;
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;++i)
lct::sz[i]=lct::val[i]=lct::sv[i]=1;
for(int i=1;i<=m;++i){
cin>>e[i].u>>e[i].v>>e[i].w;
lct::sz[i+n]=1;
lct::mx[i+n]=lct::dis[i+n]=make_pair(e[i].w,i);
}
int odd=n;
for(int i=1;i<=m;++i){
int u=e[i].u,v=e[i].v;
if(lct::connect(u,v)){
pair<int,int>qwq=lct::ask(u,v);
if(e[i].w<qwq.first){
int id=qwq.second;
lct::cut(id+n,e[id].u);
lct::cut(id+n,e[id].v);
st.erase(make_pair(e[id].w,id));
st.insert(make_pair(e[i].w,i));
lct::link(i+n,u);
lct::link(i+n,v);
}
}else{
int _1=lct::get(u),_2=lct::get(v);
if((_1&1)&&(_2&1))odd-=2;
lct::link(i+n,u);
lct::link(i+n,v);
st.insert(make_pair(e[i].w,i));
}
if(odd)cout<<"-1\n";else{
while(1){
int id=st.rbegin()->second;
lct::cut(id+n,e[id].u);
lct::cut(id+n,e[id].v);
int _1=lct::get(e[id].u),_2=lct::get(e[id].v);
if((_1&1)&&(_2&1)){
lct::link(id+n,e[id].u);
lct::link(id+n,e[id].v);
cout<<st.rbegin()->first<<'\n';
break;
}else st.erase(make_pair(e[id].w,id));
}
}
}
return 0;
}