1. 程式人生 > >[Luogu5161]WD與數列(字尾陣列/字尾自動機+線段樹合併)

[Luogu5161]WD與數列(字尾陣列/字尾自動機+線段樹合併)

https://blog.csdn.net/WAautomaton/article/details/85057257

解法一:字尾陣列

顯然將原陣列差分後答案就是所有不相交不相鄰重複子串個數+n*(n-1)/2。

答案=重複子串個數-相鄰或相交重複子串個數。

前者單調棧直接求解,注意細節,重點在後者。

由於是有關相交的計數問題,考慮類似[NOI2016]優秀的拆分的設關鍵點的做法。

列舉兩個串的偏移量k,每k個位置設一個關鍵點,我們需要保證任意兩個相距為k的重複子串都在且僅在它們覆蓋的第一個關鍵點處被計算一次。

求出每相鄰兩個關鍵點的LCP和LCS,發現答案是一個等差數列,注意式子的推導,不能重複計算。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
 5 typedef long long ll;
 6 using namespace std;
 7 
 8 const int N=300010;
 9 int n,tot,top,s[N],b[N],stk[N],lg[N];
10 ll res,sm;
11 
12 struct SA{
13     int
s[N],c[N],x[N],y[N],sa[N],rk[N],st[N][20],h[N]; 14 bool Cmp(int a,int b,int l){ return a+l<=n && b+l<=n && y[a]==y[b] && y[a+l]==y[b+l]; } 15 16 void build(int m){ 17 memset(y,0,sizeof(y)); 18 rep(i,0,m) c[i]=0; 19 rep(i,1,n) c[x[i]=s[i]]++;
20 rep(i,1,m) c[i]+=c[i-1]; 21 for (int i=n; i; i--) sa[c[x[i]]--]=i; 22 for (int k=1,p=0; p<n; k<<=1,m=p){ 23 p=0; 24 rep(i,n-k+1,n) y[++p]=i; 25 rep(i,1,n) if (sa[i]>k) y[++p]=sa[i]-k; 26 rep(i,0,m) c[i]=0; 27 rep(i,1,n) c[x[y[i]]]++; 28 rep(i,1,m) c[i]+=c[i-1]; 29 for (int i=n; i; i--) sa[c[x[y[i]]]--]=y[i]; 30 rep(i,1,n) y[i]=x[i]; x[sa[p=1]]=1; 31 rep(i,2,n) x[sa[i]]=Cmp(sa[i-1],sa[i],k) ? p : ++p; 32 } 33 } 34 35 void height(){ 36 int k=0; 37 rep(i,1,n) rk[sa[i]]=i; 38 rep(i,1,n){ 39 for (int j=sa[rk[i]-1]; i+k<=n && j+k<=n && s[i+k]==s[j+k]; k++); 40 h[rk[i]]=k; if (k) k--; 41 } 42 rep(i,1,n) st[i][0]=h[i]; 43 rep(j,1,lg[n]) rep(i,1,n-(1<<j)+1) st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]); 44 } 45 46 int que(int a,int b){ 47 int x=rk[a],y=rk[b]; 48 if (x==y) return n-a+1; 49 if (x>y) swap(x,y); 50 x++; int t=lg[y-x+1]; 51 return min(st[x][t],st[y-(1<<t)+1][t]); 52 } 53 }sa1,sa2; 54 55 int LCP(int l,int r){ return sa1.que(l,r); } 56 int LCS(int l,int r){ return sa2.que(n-r+1,n-l+1); } 57 58 int main(){ 59 freopen("P5161.in","r",stdin); 60 freopen("P5161.out","w",stdout); 61 scanf("%d",&n); 62 rep(i,2,n) lg[i]=lg[i>>1]+1; 63 rep(i,1,n) scanf("%d",&s[i]); 64 n--; rep(i,1,n) b[i]=s[i]=s[i+1]-s[i]; 65 sort(b+1,b+n+1); tot=unique(b+1,b+n+1)-b-1; 66 rep(i,1,n) s[i]=lower_bound(b+1,b+tot+1,s[i])-b; 67 rep(i,1,n) sa1.s[i]=sa2.s[n-i+1]=s[i]; 68 sa1.build(tot); sa2.build(tot); sa1.height(); sa2.height(); 69 rep(i,1,n+1){ 70 res+=sm; 71 for (; top && sa1.h[stk[top]]>=sa1.h[i]; top--) 72 sm-=1ll*(sa1.h[stk[top]]-sa1.h[i])*(stk[top]-stk[top-1]); 73 sm+=sa1.h[stk[++top]=i]; 74 } 75 rep(i,1,n){ 76 for (int j=i; j+i<=n; j+=i){ 77 int a=min(i,LCS(j,j+i)),b=LCP(j,j+i),l=min(a-1,b+a-i); 78 if (l>=0) res-=1ll*(l+1)*(b+a-i)-1ll*l*(l+1)/2; 79 } 80 } 81 printf("%lld\n",res+1ll*n*(n+1)/2); 82 return 0; 83 }

解法二:字尾自動機+線段樹合併

建出parent樹,注意到任意兩個位置的LCP就是它們在parent樹上LCA的mx。於是每個結點上用一棵線段樹維護相關資訊,同時用一個vector記錄子樹中的點。線段樹的合併直接用普通線段樹合併,vector的合併用啟發式合併。

複雜度$O(n\log^2 n)$,但由於常數小所以不會被卡。(當然用時是解法一的三倍)

 1 #include<map>
 2 #include<cstdio>
 3 #include<vector>
 4 #include<algorithm>
 5 #define lson ls[x],L,mid
 6 #define rson rs[x],mid+1,R
 7 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
 8 #define For(i,u) for (int i=h[u]; i; i=nxt[i])
 9 typedef long long ll;
10 using namespace std;
11 
12 const int N=1000010,M=20000010;
13 ll ans,v[M],vs[M];
14 int n,p,np,lst=1,nd=1,nd2,cnt,a[N],pos[N],rt[N],mx[N];
15 int fa[N],ls[M],rs[M],to[N<<1],nxt[N<<1],h[N];
16 map<int,int>son[N];
17 vector<int>ve[N],*f[N];
18 void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; }
19 
20 void ext(int c,int x){
21     p=lst; lst=np=++nd; mx[np]=mx[p]+1; pos[x]=np;
22     while (p && !son[p][c]) son[p][c]=np,p=fa[p];
23     if (!p) fa[np]=1;
24     else{
25         int q=son[p][c];
26         if (mx[q]==mx[p]+1) fa[np]=q;
27         else{
28             int nq=++nd; mx[nq]=mx[p]+1;
29             son[nq]=son[q]; fa[nq]=fa[q]; fa[q]=fa[np]=nq;
30             while (p && son[p][c]==q) son[p][c]=nq,p=fa[p];
31         }
32     }
33 }
34 
35 void mdf(int &x,int L,int R,int k){
36     if (!x) x=++nd2;
37     v[x]++; vs[x]+=k;
38     if (L==R) return;
39     int mid=(L+R)>>1;
40     if (k<=mid) mdf(lson,k); else mdf(rson,k);
41 }
42 
43 ll que(int x,int L,int R,int l,int r,int k){
44     l=max(l,L); r=min(r,R);
45     if(!x || l>r || r<1 || l>n) return 0;
46     if (L==l && r==R) return k ? vs[x] : v[x];
47     int mid=(L+R)>>1;
48     if (r<=mid) return que(lson,l,r,k);
49     else if (l>mid) return que(rson,l,r,k);
50         else return que(lson,l,mid,k)+que(rson,mid+1,r,k);
51 }
52 
53 int merge(int x,int y){
54     if (!x || !y) return x|y;
55     v[x]+=v[y]; vs[x]+=vs[y];
56     ls[x]=merge(ls[x],ls[y]);
57     rs[x]=merge(rs[x],rs[y]);
58     return x;
59 }
60 
61 void dfs(int u){
62     int k=mx[u];
63     For(i,u){
64         int v=to[i]; dfs(v);
65         if (f[u]->size()<f[v]->size()) swap(f[u],f[v]),swap(rt[u],rt[v]);
66         int ed=f[v]->size()-1;
67         rep(j,0,ed){
68             int x=f[v]->at(j);
69             ll A=que(rt[u],1,n,x-k-1,x-2,0)*(x-1)-que(rt[u],1,n,x-k-1,x-2,1)+que(rt[u],1,n,1,x-k-2,0)*k;
70             ll B=que(rt[u],1,n,x+2,x+k+1,1)-que(rt[u],1,n,x+2,x+k+1,0)*(x+1)+que(rt[u],1,n,x+k+2,n,0)*k;
71             ans+=A+B;
72         }
73         rep(j,0,ed) f[u]->push_back(f[v]->at(j));
74         rt[u]=merge(rt[u],rt[v]);
75     }
76 }
77 
78 int main(){
79     freopen("P5161.in","r",stdin);
80     freopen("P5161.out","w",stdout);
81     scanf("%d",&n);
82     rep(i,1,n) scanf("%d",&a[i]);
83     n--; rep(i,1,n) a[i]=a[i+1]-a[i];
84     ans=1ll*n*(n+1)>>1;
85     rep(i,1,n) ext(a[i],i);
86     rep(i,2,nd) add(fa[i],i);
87     rep(i,1,nd) f[i]=&ve[i];
88     rep(i,1,n) f[pos[i]]->push_back(i),mdf(rt[pos[i]],1,n,i);
89     dfs(1); printf("%lld\n",ans);
90     return 0;
91 }