【题解】CF603E Pastoral Oddities (分治)
【题解】CF603E Pastoral Oddities (分治)
考虑下这个奇怪的条件"每个点的度数都是奇数"。但这个图是一个无向图,点的总度数应该是偶数。因此对于任何一个联通块,这个联通块合法的必要条件是点的个数是偶数个,否则度数都不一样。
然而这个同时是一个必要条件,这里给出一个构造,你弄一颗dfs树出来,不妨设这些点为\(1\sim n\),\(1\)是根。按照dfs序反序考虑每个点,若一个点的度数是偶数则断开父边,否则保留这条父边。通过这样的算法我们可以直接钦定除了1的所有点的度数是奇数,由于断开一条边不改变总度数奇偶性(总度数变化量为2),那么1的度数\(\equiv 2-(n-1)\equiv n+1\pmod 2\)是奇数。
再考虑联通块之间的关系,发现两个偶联通块连不连一起无所谓,不影响答案; 两个奇联通块一定连一起这样消掉了一个;一奇一偶连在一起也无所谓。因此有边我们就连最优,而答案要求我们最大的边最小,那么这相当于做最小生成树的过程。可以lct动态维护最小生成树,也可以按时间分治。
考虑\(\rm Kruskal\)算法的过程,我们要从大到小枚举每条边,而且我们还要保证假如的边的时间在前面。考虑这样一种分治结构:
\({\rm solve}(l,r,L,R)\), 表示我要考虑\([l,r]\)时间中的答案,且这个答案范围为\([L,R]\),且并查集已经囊括了所有\(w<L\)且在时间\([l,r]\)中的边。我们将\([l,\rm mid ]\)中的边按边权依次加入得到\(\rm ans[mid]\)。
此外我们发现\(\rm ans[]\) 是单调递减的,因此我们可以确定\([l,\rm mid)\)的答案\(\ge \rm ans[mid]\),而\((mid,r]\)的答案\(\le \rm ans[mid]\)。也就是说:
对于\((mid,r]\)中的答案,所有\(> ans[\rm mid]\)的边都可以忽略;
对于\([l,\rm mid )\)的答案,\(\le \rm ans[mid]\)的所有边都会被加入并查集中。(这两个结论都是根据最小生成树的性质得到)
这也就意味着,一条边在并查集中产生对\(\rm ans[x]\)的时间是一段连续的。
那么在递归进入这两边子问题之前,把 可以忽略/必定加入 的边先加入进来再递归。这样一条边至多会被\(O(\log n)\)次求解\(\rm ans[mid]\)的过程中被加入。(一条边存在是一个区间,我们相当于把这个区间拆分成了\(O(\log n)\)个区间)
复杂度\(O(m\log m \log n )\)
//@winlere
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<map>
using namespace std; typedef long long ll;
inline int qr(){
int ret=0,f=0,c=getchar();
while(!isdigit(c)) f|=c==45,c=getchar();
while( isdigit(c)) ret=ret*10+c-48,c=getchar();
return f?-ret:ret;
}
const int maxn=1e5+5;
const int inf=0x3f3f3f3f;
int r[maxn],siz[maxn],stk[maxn],top,n,m,cnt_odd;
int Find(int x){return x==r[x]?x:Find(r[x]);}
int Merge(int x,int y){
x=Find(x),y=Find(y);
if(x==y) return 0;
if(siz[x]>siz[y]) swap(x,y);
cnt_odd-=siz[x]&1; cnt_odd-=siz[y]&1;
r[x]=y; siz[y]+=siz[x]; stk[++top]=x;
cnt_odd+=siz[y]&1;
return 1;
}
void Undo(int x=1e8){
while(x--){
int u=stk[top],v=r[u];
cnt_odd-=siz[v]&1;
siz[v]-=siz[u];
cnt_odd+=siz[v]&1; cnt_odd+=siz[u]&1;
r[u]=u;
top--;
}
}
int ans[maxn*3];
struct E{int u,v,w;}e[maxn*3];
map<int,vector<int>> C;
#define mid ((l+r)>>1)
void solve(int l,int r,int L,int R){
if(L>R) return;
int base=0,sav=0,sav1=0,ret=R;
for(int t=l;t<=mid;++t)
if(e[t].w<L) base+=Merge(e[t].u,e[t].v);
for(auto it=C.lower_bound(L),ed=C.upper_bound(R);it!=ed;++it)
for(auto t:it->second)
if(t<=mid){
if( cnt_odd) sav+=Merge(e[t].u,e[t].v);
if(!cnt_odd) {ret=e[t].w;it=prev(ed);break;}
}
ans[mid]=ret; Undo(sav);
if(mid<r) solve(mid+1,r,L,ret);
Undo(base);
for(auto it=C.lower_bound(L),ed=C.lower_bound(ret);it!=ed;++it)
for(auto t:it->second)
if(t<l) sav1+=Merge(e[t].u,e[t].v);
if(l<mid) solve(l,mid-1,ret,R);
Undo(sav1);
}
#undef mid
int main(){
memset(ans,0x3f,sizeof ans);
n=qr(),m=qr();
for(int t=1;t<=n;++t) r[t]=t,siz[t]=1;
cnt_odd=n;
for(int t=1;t<=m;++t) e[t].u=qr(),e[t].v=qr(),e[t].w=qr(),C[e[t].w].push_back(t);
solve(1,m,1,inf);
for(int t=1;t<=m;++t) printf("%d\n",ans[t]==inf?-1:ans[t]);
return 0;
}