正文
現在有以下這個問題:
給定,對於,我們需要將和併到一起。
考慮類似RMQ的做法:
建個並查集,第i個並查集的和聯通表示所有與聯通()。也就是說,與聯通,與聯通,與聯通……
這樣子就實現了原問題。
具體操作的時候,令操作爲 {l,y,z},表示需要對於任意的,將與聯通
如果第z個並查集的與已經聯通,則退出。
否則
如果z=0,直接聯通,否則遞歸執行第z-1個並查集。
每個並查集的每個層均攤次,複雜度
事實上除了 將和併到一起 這個問題以外,其它對一段區間進行完全相同操作的詢問也可以類似處理。
代碼
#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);
}
}