一种并查集操作

43 篇文章 0 订阅
6 篇文章 0 订阅

基础并查集链接


正文

现在有以下这个问题:
给定 l , r , y l,r,y l,r,y,对于 x ∈ [ l , r ] x\in [l,r] x[l,r],我们需要将 x x x x + y x+y x+y并到一起。
考虑类似RMQ的做法:
l o g n logn logn个并查集,第i个并查集的 x x x y y y联通表示所有 x + j x+j x+j y + j y+j y+j联通( 0 ≤ j < 2 i 0\leq j < 2^i 0j<2i)。也就是说, x x x y y y联通, x + 1 x+1 x+1 y + 1 y+1 y+1联通, x + 2 x+2 x+2 y + 2 y+2 y+2联通……
这样子就实现了原问题。
具体操作的时候,令操作为 {l,y,z},表示需要对于任意的 x ∈ [ l , l + 2 z ) x\in [l,l+2^z) x[l,l+2z),将 x x x y y y联通
如果第z个并查集的 x x x y y y已经联通,则退出。
否则
如果z=0,直接联通,否则递归执行第z-1个并查集。
每个并查集的每个层均摊 O ( n ) O(n) O(n)次,复杂度 O ( n l o g n ) O(n log n) O(nlogn)
事实上除了 x x x x + y x+y x+y并到一起 这个问题以外,其它对一段区间进行完全相同操作的询问也可以类似处理。

代码

#define fo(i,a,b) for(int i=a;i<=b;i++)
struct disjoint_set{
	int fa[N];
	int gf(int x){return fa[x]==x?x:fa[x]=gf(fa[fa[fa[x]]]);}
	void reset(){fo(i,1,n) fa[i]=i;}
	void link(int x,int y){fa[gf(x)]=gf(y);}
	bool linked(int x,int y){return gf(x)==gf(y);}
}b[20];

void work(int x,int y,int z)
{
	if(b[z].linked(x,x+y)) return;
	b[z].link(x,x+y);
	if(z==0) ans=ans+W,ans1++;
	else work(x,y,z-1),work(x+1<<(z-1),y,z-1);
}

例题【GDOI2019Day2模拟2019.4.29】Endless

Description

在这里插入图片描述

Input

在这里插入图片描述

Output

在这里插入图片描述

Sample Input

1
8
2 2 5 6 2 5 6 2
5 1 4 4

Sample Output

21

Solution

按照kruskal的做法,对不同长度的边权排序。
设目前处理长度为L的连边。
将序列每L个位置放一个分界点。
相邻两个分界点求最长公共前缀和最长公共后缀。
就可以求出合法的长度为2L 的区间
然后就是上面那个并查集问题了。

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define N 1001000
#define ull unsigned long long
#define mo 1000000009ll
#define ll long long
using namespace std;
int a[N],n,lg[N],_[N],ans1=0;
ull s[N],e[N];
ll ans,W;
struct disjoint_set{
	int fa[N];
	int gf(int x){return fa[x]==x?x:fa[x]=gf(fa[fa[fa[x]]]);}
	void reset(){fo(i,1,n) fa[i]=i;}
	void link(int x,int y){fa[gf(x)]=gf(y);}
	bool linked(int x,int y){return gf(x)==gf(y);}
}b[20];
pair<ll,int>w[N];
ull get(int x,int y)
{
	if(x>y) swap(x,y);
	return (s[y]-s[x-1]*e[y-x+1]%mo+mo)%mo;
}
int ef(int x,int y,int r,int z)
{
	r++;
	if(a[x]!=a[y]) return 0;
	int l=1;
	while(l+1<r)
	{
		int m=(l+r)>>1;
		if(get(x,x+(m-1)*z)==get(y,y+(m-1)*z)) l=m;else r=m;
	}
	return l;
}
void work(int x,int y,int z)
{
	if(b[z].linked(x,x+y)) return;
	b[z].link(x,x+y);
	if(z==0) ans=ans+W,ans1++;
	else work(x,y,z-1),work(x+_[z-1],y,z-1);
}
int main()
{
	freopen("endless.in","r",stdin);
//	freopen("endless.out","w",stdout);
	_[0]=1;
	fo(i,1,19)
	{
		_[i]=_[i-1]*2;
		fo(j,_[i-1],_[i]-1) lg[j]=i-1;
	}
	int ac;scanf("%d",&ac);
	while(ac--)
	{
		scanf("%d",&n);
		fo(i,0,18) b[i].reset();
		e[0]=1,ans=0;
		fo(i,1,n) scanf("%d",&a[i]),s[i]=(s[i-1]*(ull)(19260819)+(ull)a[i])%mo,e[i]=(e[i-1]*(ull)(19260819))%mo;
		fo(i,1,n/2) scanf("%lld",&w[i].first),w[i].second=i;
		sort(w+1,w+n/2+1);
		fo(q,1,n/2)
		{
			int L=w[q].second;
			for(int i=1;i+L<=n;i+=L)
			{
				int j=i+L;
				int r=ef(i,j,n-j+1,1),l=ef(i,j,i,-1);
				if(l+r<=L) continue;
				l=i-l+1,r=i+r-1;
				int z=lg[r-l+1];
				W=w[q].first;
				work(l,L,z);
				work(r-_[z]+1,L,z);
			}
		}
		printf("%lld\n",ans);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值