题解[CF891C Envy]

题目链接

题意:给定一张无向连通图,每次给出一些边,询问这些边能否同时在一个最小生成树中出现。

先建出任意一个最小生成树,对树上与非树上的边讨论。

首先,如果询问的边会构成一个环,那就完全不可能在同一最小生成树中。

其次,考虑一条非树边 ((x,y,w)) 什么时候能替换一条在原先最小生成树上的边。

那就必须满足 (x,y) 两个点在原先最小生成树路径上的边的最大值 (geq) (w) ,不然不可行。

那这时候这条边就能将这条边权最大的边替换掉。

但如果这条边权最大的边被要求了必须出现,就需要另外的边去替换。

由于有动态的替换与求路径最大值,可以考虑 ( ext{LCT}) 化边为点实现。

先将询问中在原最小生成树上的边的权值设为 (0) 保证不会被替换。

再对非树边查路径最大值看能否替换,并记录下被替换过的边,在最终要复原回去。

这样的时间复杂度是 (O(nlog n)) 的(默认 (n,m) 同阶)。

但有于 ( ext{LCT}) 的巨大常数,且为了查路径最值的化边为点,点数已去到 (1e6) 级别。

所以这种方法特别卡常,顺手最劣解

最终代码:

#pragma GCC optimize("Ofast")
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int n,m,q,x,y,v,k,ax,ay,tot;bool flag,bb[N];
int w[N],ww[N],xx[N],yy[N],a[N],f[N],id[N];
inline int getf(int x){while(x^f[x])x=f[x]=f[f[x]];return x;}
struct pre_state{int x,y;}ps[N<<1];
namespace FastIO
{//此处省略带缓存的 FastIO
}
using namespace FastIO;
struct lct{
	int mxid[N],son[N][2],tmpid,anc[N],rev[N],i,y,_y,_x,_id,rx,ry,mxy;bool b;
	inline bool nroot(int x){return son[anc[x]][0]==x||son[anc[x]][1]==x;}
	inline bool p(int x){return son[anc[x]][1]==x;}
	inline void rev_(int x){rev[x]^=1;son[x][0]^=son[x][1]^=son[x][0]^=son[x][1];}
	inline void fix(int x){
		mxid[x]=x;
		if(w[tmpid=mxid[son[x][0]]]>w[x])mxid[x]=tmpid;
		if(w[tmpid=mxid[son[x][1]]]>w[mxid[x]])mxid[x]=tmpid;
	}
	inline void pushdown(int x){
		if(rev[x]){if(son[x][0])rev_(son[x][0]);if(son[x][1])rev_(son[x][1]);rev[x]=0;}
	}
	void pushall(int x){if(nroot(x))pushall(anc[x]);pushdown(x);}
	inline void rotate(int x){
		y=anc[x];_x=anc[y];b=p(x);
		if(nroot(y))son[_x][p(y)]=x;
		anc[x]=_x;
		anc[son[y][b]=son[x][!b]]=y;
		anc[son[x][!b]=y]=x;
		fix(y);fix(x);fix(_x);
	}
	inline void splay(int x){
		pushall(x);for(;i=anc[x],nroot(x);rotate(x))if(nroot(i))rotate(p(x)==p(i)?i:x);
	}
	inline void access(int x){
		for(_y=0;x;_y=x,x=anc[x])splay(x),son[x][1]=_y,fix(x);
	}
	inline void makeroot(int x){access(x);splay(x);rev_(x);}
	inline void split(int x,int y){makeroot(x);access(y);splay(y);}
	inline void link(int x,int y){makeroot(x);anc[x]=y;}
	inline void cut(int x,int y){split(x,y);son[y][0]=anc[x]=0;fix(y);}
	inline void link(int x,int y,int id){
		split(x,y);mxy=mxid[y];
		if(w[mxy]>=w[id]){
			_id=mxy;
			cut(xx[_id],_id);cut(_id,yy[_id]);
			link(x,id);link(id,y);
			ps[++tot]=(pre_state){_id,id};
		}
		else flag=1;
	}
	inline void recover(){
		for(;tot;--tot){
			ry=ps[tot].y,rx=ps[tot].x;
			cut(xx[ry],ry);cut(yy[ry],ry);
			link(xx[rx],rx);link(yy[rx],rx);
		}
	}
	void getbb(){for(int i=1;i<=m;++i)split(xx[i],i),bb[i]=son[i][0]==xx[i];}
}L;
bool cmp(int a,int b){return w[a]<w[b];}
main(){
	qin>>n>>m;register int i;
	for(i=1;i<=m;++i)qin>>xx[i]>>yy[i]>>w[i];
	for(i=1;i<=m;++i)xx[i]+=m,yy[i]+=m,ww[i]=w[i],id[i]=i;
	for(i=1;i<=n;++i)f[i]=i;
	sort(id+1,id+m+1,cmp);
	for(i=1;i<=m;++i){
		x=xx[id[i]]-m,y=yy[id[i]]-m;
		ax=getf(x),ay=getf(y);
		if(ax==ay)continue;
		f[ax]=ay;
		L.link(x+m,id[i]);L.link(y+m,id[i]);
	}//老老实实打kruskal
	L.getbb();tot=0;qin>>q;
	for(i=1;i<=n;++i)f[i]=i;
	while(q--){
		qin>>k;flag=1;
		for(i=1;i<=k;++i)qin>>a[i],flag&=bb[a[i]];
		if(flag){puts("YES");continue;}//全部在原MST的边显然可行
		flag=0;
		for(i=1;i<=k;++i){
			ax=getf(xx[a[i]]-m);
			ay=getf(yy[a[i]]-m);
			if(ax==ay)flag=1;
			else f[ax]=ay;
		}//并查集判环
		for(i=1;i<=k;++i)x=xx[a[i]]-m,f[x]=x,y=yy[a[i]]-m,f[y]=y;
		if(flag){puts("NO");continue;}
		for(i=1;i<=k;++i)if(bb[a[i]])w[a[i]]=0,L.splay(a[i]);
		for(i=1;i<=k;++i)if(!bb[a[i]]){
			L.link(xx[a[i]],yy[a[i]],a[i]);
			if(flag)break;
		}
		for(i=1;i<=k;++i)if(bb[a[i]])w[a[i]]=ww[a[i]],L.splay(a[i]);
		L.recover();//复原
		if(flag)puts("NO");
		else puts("YES");
	}
}
原文地址:https://www.cnblogs.com/Y-B-X/p/15424366.html