正文
现在有以下这个问题:
给定
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);
}
例题【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);
}
}