題解
SAM+01Trie樹合併的模板題(兩樣東西我都不太會,寫了我一下午,我太菜了5555……)
先考慮反着建SAM,得到的fail樹就是原串的後綴樹
後綴樹上兩個點的LCA的endpos集合的最大長度就是這兩個點的LCP長度
這樣我們就解決了第一個值LCP(x,y)
考慮dfs一遍後綴樹,那麼每到一個節點,它的子樹中的所有點對的貢獻中的LCP(x,y)都是固定的了
如何求一個子樹中所有節點權值的兩兩異或最大值呢?
用01Trie樹來進行貪心,先儘量走不同方向,再走相同方向
但是如果在每個節點都開一個01Trie樹並把所有子節點都加進來,複雜度會爆炸
我們考慮把所有兒子的01Trie樹來合併到一起
合併的時候可以不用新建節點來可持久化(所以爲什麼放在了可持久化作業裏a。。。)
(如果建了新點就是可持久化01Trie樹合併,空間複雜度爲O(nlogn))
(事實上大部分題都是需要新建點的,否則會出許多奇奇怪怪的問題)
這樣的複雜度分析類似於線段樹合併,是O(nlogn)的
因爲我們只有在兩棵線段樹都存在節點的時候合併兩棵線段樹的答案
一次合併的複雜度其實是較小的線段樹的節點個數(查詢的複雜度也是)
查詢的時候應該在合併之前查詢,否則時間複雜度會從均攤的O(nlogn)退化爲O(n^2)
這是有的人就會問了,“一次合併的複雜度其實是較小的線段樹的節點個數”,那麼總的合併次數就應該是O(nlogn)的
而set啓發式合併也是隻合併了O(nlogn)次,那爲什麼set合併的複雜度爲O(nlog^2n)呢?
因爲線段樹合併一次是均攤O(minsiz*c)的(minsiz爲較小的點集的大小,c是常數)
而set合併是一個一個暴力插入的是O(minsiz*log(maxsiz))的
各種合併算法的時間複雜度分析
這裏不得不提到一些關於合併的問題
長鏈剖分合並兩條鏈時的合併次數是均攤O(n)的
因爲一條長度爲5的鏈合併到一條長度爲10的鏈得到的是長度爲10的鏈
也就是說每條鏈只會在向鏈頂父親合併時會貢獻O(鏈長)的合併次數
而總鏈長爲O(n),時間複雜度也就是O(n*一次insert的複雜度)
重鏈剖分合並兩棵子樹是的合併次數是均攤O(nlogn)的
因爲一棵大小爲5的子樹合併到一棵大小爲10的子樹得到的是大小爲15的子樹
也就是說每個top點只會在向其父親合併時會貢獻O(子樹大小)的合併次數
均攤到每個點的身上,就相當於每個點都向上爬鏈,一直爬到根,經過的輕邊次數就是它在合併中貢獻的合併次數
由於一個點到根路徑最多經過O(logn)條輕邊,所以總合並次數就是均攤O(nlogn)
總時間複雜度就是O(nlogn*一次insert的複雜度)
其實啓發式合併的原理與這個大同小異
本題代碼:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 200005
int fa[N],ch[N][26],mxlen[N],id[N],last,sz;
void extend(int x,int pos)
{
int np,p,nq,q;
p=last;np=++sz;
mxlen[np]=mxlen[p]+1;id[np]=pos;
for(;p!=-1&&!ch[p][x];p=fa[p])
ch[p][x]=np;
if(p==-1)fa[np]=0;
else{
q=ch[p][x];
if(mxlen[q]==mxlen[p]+1)fa[np]=q;
else{
nq=++sz;
mxlen[nq]=mxlen[p]+1;
memcpy(ch[nq],ch[q],sizeof(ch[q]));fa[nq]=fa[q];
for(;p!=-1&&ch[p][x]==q;p=fa[p])ch[p][x]=nq;
fa[np]=fa[q]=nq;
}
}
last=np;
}
char s[N];int w[N];
int fir[N],to[N],nxt[N],cnt;
void adde(int a,int b){to[++cnt]=b;nxt[cnt]=fir[a];fir[a]=cnt;}
struct node{
int ch[2];
}a[N<<5];
int T[N],tot;
void insert(int &i,int x,int d)
{
if(!i)i=++tot;
if(d==-1)return;
insert(a[i].ch[(x>>d)&1],x,d-1);
}
int ans;
void query(int i,int j,int sum,int d)
{
if(d==-1){ans=max(sum,ans);return;}
if((a[i].ch[0]&&a[j].ch[1])||(a[i].ch[1]&&a[j].ch[0])){
if(a[i].ch[0]&&a[j].ch[1])
query(a[i].ch[0],a[j].ch[1],sum+(1<<d),d-1);
if(a[i].ch[1]&&a[j].ch[0])
query(a[i].ch[1],a[j].ch[0],sum+(1<<d),d-1);
}
else{
if(a[i].ch[0]&&a[j].ch[0])
query(a[i].ch[0],a[j].ch[0],sum,d-1);
if(a[i].ch[1]&&a[j].ch[1])
query(a[i].ch[1],a[j].ch[1],sum,d-1);
}
}
int merge(int i,int j)
{
if(!i||!j)return i+j;
a[i].ch[0]=merge(a[i].ch[0],a[j].ch[0]);
a[i].ch[1]=merge(a[i].ch[1],a[j].ch[1]);
return i;
}
int siz[N];
void solve(int u)
{
if(id[u])insert(T[u],w[id[u]],16),siz[u]=1;
for(int v,p=fir[u];p;p=nxt[p]){
v=to[p];solve(v);
//if(siz[u]<siz[v])swap(T[u],T[v]);
query(T[u],T[v],mxlen[u],16);
T[u]=merge(T[u],T[v]);siz[u]+=siz[v];
}
}
int main()
{
last=0;fa[0]=-1;
int n,i;
scanf("%d%s",&n,s+1);
for(i=n;i>=1;i--)extend(s[i]-'a',i);
for(i=1;i<=n;i++)scanf("%d",&w[i]);
for(i=1;i<=sz;i++)adde(fa[i],i);
solve(0);
printf("%d\n",ans);
}