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


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章