【APIO2019】桥梁(并查集)(暴力)(根号分治)

传送门


看了T2才知道今年供题是Russia。

有一次在CF上看到Um_nik和dls在讨论ICPC,当时听说国际比赛不敢出数据结构大题,甚至连码量大的题,或者计算机理论要求比较高的题都不敢出,因为不清楚哪些国家在某些套路上有什么偏好,做APIO的时候感觉无比真实。。。

但是这道题正解是不是太明显了一点,China肯定一堆人切了吧

可惜我没去APIO不然就200+了

题解:

第一感觉是线段树分治,但是仔细想了想没有办法维护连通块大小,如果是连通块个数就很好做。。。

看了下数据范围可能是根号分治,然后正解就真的是根号分治

根号分治的话,要考虑两种暴力,修改少询问多,询问少修改多。

暴力一:修改暴力,询问就每次把合法边加到并查集里面看siz就行了。
暴力二:首先把没有修改过的边拿出来,按照 d d d排序,把询问拿出来,按照 d d d排序,然后顺序处理,没有修改的边直接加到并查集里面即可,有修改的边拿出来,把这个询问之前的修改操作,然后看有哪些边能加到并查集里面去,需要可回退化并查集。

每根号个操作拿出来做一遍,然后把修改操作上去即可。

块大小需要设置成根号下面套一个log才能到达理论最优。

不过因为常数问题可以自己夹带私货,比如乘个0.8什么的


代码:

#include
#define ll long long
#define re register
#define cs const

namespace IO{
	inline char gc(){
		static cs int Rlen=1<<22|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}
	
	template<typename T>
	inline T get(){
		char c;T num;
		while(!isdigit(c=gc()));num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return num;
	}
	inline int gi(){return get<int>();}
}
using namespace IO;

using std::cerr;
using std::cout;

cs int M=1e5+7,N=5e4+7,S=700,P=S+7;
struct Edge{int u,v,d,id;};
struct Query{int u,d,id;};
Edge E[M],E1[M],E2[P];
Query Q[P];

int n,m,q;
int op[M],a[M],b[M];
int d[M],ori[M],ans[M];
int fa[N],sz[N];
int *st[N],pr[N],tp;
bool vis[M];

inline int gf(int u){while(fa[u]>0)u=fa[u];return u;}
inline int gf_(int u){while(fa[u]>0&&fa[fa[u]]>0)u=fa[u]=fa[fa[u]];return fa[u]>0?fa[u]:u;}
inline void pack(int &u){st[++tp]=&u,pr[tp]=u;}
inline void merge(int u,int v){
	u=gf_(u),v=gf_(v);if(u==v)return ;
	if(fa[u]>fa[v])std::swap(u,v);
	if(fa[v]==fa[u])--fa[u];
	fa[v]=u,sz[u]+=sz[v];
}
inline void _merge(int u,int v){
	u=gf(u),v=gf(v);if(u==v)return ;
	if(fa[u]>fa[v])std::swap(u,v);
	if(fa[v]==fa[u])pack(fa[u]),--fa[u];
	pack(fa[v]);fa[v]=u;
	pack(sz[u]);sz[u]+=sz[v];
}

inline void solve(int l,int r){
	int t1=0,t2=0,q=0;
	for(int re i=l;i<r;++i)
	if(op[i]==1)vis[a[i]]=true;
	else Q[q++]=(Query){a[i],b[i],i};
	for(int re i=0;i<m;++i)
	(vis[E[i].id]?E2[t2++]:E1[t1++])=E[i];
	memset(fa,0,n+1<<2);
	std::fill(sz,sz+n+1,1);
	std::sort(Q,Q+q,[](cs Query &a,cs Query &b){
		return a.d>b.d;
	});
	for(int re j=0,k=0;k<q;++k){
		while(j<t1&&Q[k].d<=E1[j].d)
		merge(E1[j].u,E1[j].v),++j;
		for(int re i=l;i<Q[k].id;++i)
		if(op[i]==1)d[a[i]]=b[i];
		for(int re i=0;i<t2;++i)
			if(Q[k].d<=d[E2[i].id])
			_merge(E2[i].u,E2[i].v);
		ans[Q[k].id]=sz[gf(Q[k].u)];
		while(tp)*st[tp]=pr[tp],--tp;
		for(int re i=l;i<Q[k].id;++i)
		if(op[i]==1)d[a[i]]=ori[a[i]];
	}
	for(int re i=l;i<r;++i)
	if(op[i]==1)vis[a[i]]=false,d[a[i]]=ori[a[i]]=b[i];
	for(int re j=0;j<t2;++j)
	E2[j].d=d[E2[j].id];
	std::sort(E2,E2+t2,[](cs Edge &a,cs Edge &b){
		return a.d>b.d;
	});
	std::merge(E1,E1+t1,E2,E2+t2,E,[](cs Edge &a,cs Edge &b){
		return a.d>b.d;
	});
}

signed main(){
#ifdef zxyoi
	freopen("bridge.in","r",stdin);
#endif
	n=gi(),m=gi();
	for(int re i=1;i<=m;++i){
		int u=gi(),v=gi();d[i]=ori[i]=gi();
		E[i-1]=(Edge){u,v,d[i],i};
	}q=gi();
	for(int re i=0;i<q;++i)op[i]=gi(),a[i]=gi(),b[i]=gi();
	std::sort(E,E+m,[](cs Edge &a,cs Edge &b){
		return a.d>b.d;
	});
	for(int re i=0;i<q;i+=S)solve(i,std::min(i+S,q));
	for(int re i=0;i<q;++i)if(op[i]==2)cout<<ans[i]<<"\n";
	return 0;
}

你可能感兴趣的:(并查集)