CF603E Pastoral Oddities

诡异的CDQ分治题,话说CDQ分治的定义到底是什么?

首先考虑判断一个边集的某个子集是否合法,稍微画下图我们就发现此时需要满足每个联通块的大小都是偶数

证明:我们考虑一个联通块内部的边带来的度数总贡献是偶数,若联通块大小为奇数的话必然会有至少一个点被分配到偶数的度数

接下来我们考虑没有加入边操作时如何算答案,很显然最大边最小我们想到排序后把边一条条加进去

用并查集维护每个联通块的大小的奇偶性,我们发现每次合并时若两个都是奇数肯定要合并

若两个都是偶数合并也也无所谓,一奇一偶合并了也不会让答案更劣,因此我们此时无论怎样都合并即可

当加入完某条边之后所有联通块的大小都是偶数时这条边就是答案了

考虑有动态加入怎么处理,有一个显然的性质:随着边的不断加入答案一定在不断变小

考虑CDQ分治,设当前处理的区间为\([l,r]\),答案的可能范围为\([mi,mx]\)

首先我们要把\([1,l)\)内的所有边权小于等于\(mi\)的边都加入并查集

考虑前求出\(mid\)的答案,此时需要先把\([l,mid]\)中所有边权小于等于\(mi\)的边都加入

然后枚举\([1,mid]\)中所有边权在\([mi,mx]\)中的边,一条一条加入,此时若所有联通块的大小都是偶数这条边就是\(mid\)的答案了

接下来考虑分治处理子区间,首先还原并查集,接着把\([l,mid]\)中所有边权小于等于\(mi\)的边都加入,去做右区间,答案的范围为\([mi,ans_{mid}]\)

再还原并查集,把\([1,l)\)中所有边权在\([mi,ans_{mid}]\)的边都加入,去做左区间,答案的范围为\([ans_{mid},mx]\)

实现的时候并不需要记录\(mx\),只要有解显然就一定会加到上界

并查集使用按秩合并撤销,总复杂度\(O(m\log m\log n)\)

#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=300005;
struct edge
{
	int x,y,v,p;
	friend inline bool operator < (const edge& A,const edge& B)
	{
		return A.v<B.v;
	}
}a[N],b[N]; int n,m,tot,ans[N];
namespace S //Union Find Set
{
	struct element
	{
		int x,y,fy,szx,tot; bool vx;
	}stk[N]; int fa[N],sz[N],top; bool v[N];
	inline int getfa(CI x)
	{
		return fa[x]!=x?getfa(fa[x]):x;
	}
	inline void Union(int x,int y)
	{
		x=getfa(x); y=getfa(y); if (x==y) return;
		if (sz[x]<sz[y]) swap(x,y); stk[++top]=(element){x,y,fa[y],sz[x],tot,v[x]};
		fa[y]=x; sz[x]+=sz[x]==sz[y]; if (v[x]&&v[y]) tot-=2; v[x]^=v[y];
	}
	inline void revoke(CI lim)
	{
		while (top>lim)
		{
			element nw=stk[top--]; fa[nw.y]=nw.fy;
			sz[nw.x]=nw.szx; tot=nw.tot; v[nw.x]=nw.vx;
		}
	}
};
inline void solve(CI l,CI r,CI mi)
{
	RI i; if (l>r) return; int mid=l+r>>1,tp=S::top;
	for (i=l;i<=mid;++i) if (b[i].p<=mi) S::Union(b[i].x,b[i].y);
	for (i=mi;i<=m;++i) if (a[i].p<=mid)
	if (S::Union(a[i].x,a[i].y),!tot) { ans[mid]=i; break; }
	for (S::revoke(tp),i=l;i<=mid;++i)
	if (b[i].p<=mi) S::Union(b[i].x,b[i].y); solve(mid+1,r,mi);
	S::revoke(tp); if (!ans[mid]) return;
	for (i=mi;i<=ans[mid];++i) if (a[i].p<l) S::Union(a[i].x,a[i].y);
	solve(l,mid-1,ans[mid]); S::revoke(tp);
}
int main()
{
	RI i; for (scanf("%d%d",&n,&m),i=1;i<=n;++i) S::fa[i]=i,S::sz[i]=S::v[i]=1;
	for (tot=n,i=1;i<=m;++i) scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].v),a[i].p=i,b[i]=a[i];
	for (sort(a+1,a+m+1),i=1;i<=m;++i) b[a[i].p].p=i; solve(1,m,1);
	for (i=1;i<=m;++i) if (ans[i]) printf("%d\n",a[ans[i]].v); else puts("-1");
	return 0;
}
posted @ 2020-12-03 15:10  空気力学の詩  阅读(95)  评论(0编辑  收藏  举报