【题解】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;
}


posted @ 2020-06-01 19:19  谁是鸽王  阅读(288)  评论(0编辑  收藏  举报