【CF603E】Pastoral Oddities(CDQ分治)

点此看题面

  • 给定一张(n)个点的图,依次往里面加入(m)条边,每条边有一个边权。
  • 每加入一条边之后,都要先判断是否存在一个边集使得所有点度数都是奇数,然后求出这个边集最大边权的最小值。
  • (nle10^5,mle3 imes10^5)

符合要求的充要条件

先给出结论吧,一个边集删掉若干条边后能符合要求的充要条件就是所有连通块点数都是偶数

必要性

考虑一条边会给总度数带来两点贡献,因此一个连通块的总度数必然是个偶数。

如果存在奇数连通块,若要让其中所有点度数都是奇数,则这个连通块的总度数就是奇数了,不可能存在。

充分性

对于任意一个偶数连通块,我们先随便找出它的一棵生成树。

然后从叶节点开始往上,如果一个点的度数是偶数,就断开它到父亲的树边,否则不动。

显然这样一来肯定能够保证除根节点外所有点的度数都是奇数了,也就是说有奇数个度数为奇数的点,根据前面的结论根节点的度数也必然为奇数。

边固定时的询问

考虑边固定的时候的做法,就是一个贪心+并查集的过程。

显然两个偶数连通块、一奇一偶连通块的合并不仅不会影响原本的奇偶性,而且可能还会便于之后的连边,把它们连起来一定不会变劣。而两个奇数连通块的合并又一定是优的。

于是得出结论,我们先把所有边按边权排序,然后依次加入每条边,当加完某条边之后所有连通块点数都是偶数了,则这条边的边权就是答案。

(CDQ)分治

感觉这种题目应该很容易就会想到用(CDQ)分治,但又想不到究竟如何分治。

一个显然的性质,就是随着边的不断加入答案肯定是逐渐减小的,因为原先的答案必然合法。

所以我们假设当前分治区间是([l,r]),然后这个区间的答案可能范围为([Mn,Mx])

做到一个区间之前,我们需要先保证所有([1,l-1])中边权小于等于(Mn)的边都已经加入到了并查集中。

我们考虑先求出(mid)的答案,那么需要先把([l,mid])中所有边权小于等于(Mn)的边给加到并查集中。

接下来,按边权从小到大枚举边权在([Mn,Mx])范围内的所有([1,mid])中的边,一条一条加到并查集中,当加完某一条所有连通块点数都是偶数了,那么这条边的边权就是第(mid)条边的答案。

知道答案之后接下来就是继续递归了。

先还原并查集,接着把([l,mid])中所有边权小于等于(Mn)的边加到并查集中,去做右区间,答案范围为([Mn,ans[mid]])

再还原并查集,接着把([1,l-1])中所有边权在([Mn,ans[mid]])中的边加到并查集中,去做左区间,答案范围为([ans[mid],Mx])

最后还要记得再还原并查集。

复杂度证明可以把([l,r])([Mn,Mx])两个区间分开来讨论,发现复杂度显然是正确的。

注意,具体实现中其实完全没必要记录(Mx),因为只要有解,就一定会在加到某条边权不超过(Mx)的边时达成条件。

代码:(O(mlogmlogn))

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define M 300000
using namespace std;
int n,m;struct line {int p,x,y,v;I bool operator < (Con line& o) Con {return v<o.v;}}s[M+5];
namespace U//按秩合并可撤销并查集
{
	int tot,f[N+5],g[N+5],v[N+5];I int fa(CI x) {return f[x]?fa(f[x]):x;}
	int T,Sx[M+5],Sy[M+5],Sg[M+5],Sv[M+5];I void Union(RI x,RI y)
	{
		if(++T,(x=fa(x))==(y=fa(y))) return (void)(Sx[T]=-1);;
		g[x]<g[y]&&(x^=y^=x^=y),Sx[T]=x,Sy[T]=y,Sg[T]=g[x],Sv[T]=v[x],
		v[x]&&v[y]&&(tot-=2),g[x]==g[y]&&++g[x],v[x]^=v[y],f[y]=x;//合并两个奇数连通块时给个数减2
	}
	I void Back() {~Sx[T]&&(f[Sy[T]]=0,g[Sx[T]]=Sg[T],(v[Sx[T]]=Sv[T])&&v[Sy[T]]&&(tot+=2)),--T;}
}
int ans[M+5];line p[M+5];I void Solve(CI l,CI r,CI Mn)//CDQ分治
{
	#define Revoke() W(U::T^O) U::Back()//还原并查集
	if(l>r) return;RI i,mid=l+r>>1,O=U::T;
	for(i=l;i<=mid;++i) s[i].p<=Mn&&(U::Union(s[i].x,s[i].y),0);//加入[l,mid]中边权小于等于Mn的边
	for(i=Mn;i<=m;++i) if(p[i].p<=mid&&(U::Union(p[i].x,p[i].y),0),!U::tot) {ans[mid]=i;break;}//依次加入[1,mid]中的边
	Revoke();for(i=l;i<=mid;++i) s[i].p<=Mn&&(U::Union(s[i].x,s[i].y),0);Solve(mid+1,r,Mn);//递归右区间
	Revoke();if(!ans[mid]) return;for(i=Mn;i<=ans[mid];++i) p[i].p<l&&(U::Union(p[i].x,p[i].y),0);//递归左区间
	Solve(l,mid-1,ans[mid]);Revoke();
}
int main()
{
	RI i;for(scanf("%d%d",&n,&m),U::tot=n,i=1;i<=n;++i) U::v[i]=1;//一开始所有点都是一个奇数连通块
	for(i=1;i<=m;++i) scanf("%d%d%d",&s[i].x,&s[i].y,&s[i].v),(p[i]=s[i]).p=i;
	for(sort(p+1,p+m+1),i=1;i<=m;++i) s[p[i].p].p=i;//按边权排序,求出原本每条边的排名
	for(Solve(1,m,1),i=1;i<=m;++i) ans[i]?printf("%d
",p[ans[i]].v):puts("-1");return 0;
}
原文地址:https://www.cnblogs.com/chenxiaoran666/p/CF603E.html