基础并查集链接
现在有以下这个问题:
给定 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 0≤j<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);
}
1
8
2 2 5 6 2 5 6 2
5 1 4 4
21
按照kruskal的做法,对不同长度的边权排序。
设目前处理长度为L的连边。
将序列每L个位置放一个分界点。
相邻两个分界点求最长公共前缀和最长公共后缀。
就可以求出合法的长度为2L 的区间
然后就是上面那个并查集问题了。
#include
#include
#include
#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);
}
}