洛谷P2463 [SDOI2008]Sandy的卡片(后缀数组SA + 差分 + 二分答案)
题目链接:https://www.luogu.org/problem/P2463
【题意】
求出N个串中都出现的相同子串的最长长度,相同子串的定义如题:所有元素加上一个数变成另一个,则这两个串相同,可以很简单的得出,差分后的串相同即相同。
【思路】
首先肯定是要对N个串分别进行差分,然后将N个串合并成一个串,首尾相接即可,但要标记那些数属于哪一个Mi(后边要进行check),这里呢要注意,记得将串分隔开来,
不然会WA,这里我用的分隔方法是在串之间加0,合并完成后,题目就可变成求最长的不重叠的重复N次的最长子串长度。因为如果长度为k的串出现了N次,那么他的前缀
也一定出现了N次,所以此题满足单调性,可以进行二分答案。
【check方法】
找到一段排名连续的[l, r]都满足height[i]>=mid( l<i<=r )的连续的区间,对该区间[l, r]进行处理,判断所有的sa[i]( l<=i<=r )的所属Mi,如果有N个不同的Mi即满足条件,返回true,
反之返回false。
上代码
#include <bits/stdc++.h> using namespace std; const int maxn = 1e6 + 5; int n, t, M, b[maxn], num[110], a[maxn]; int sa[maxn], x[maxn], c[maxn], y[maxn], rk[maxn], height[maxn]; bool vis[maxn]; inline void get_sa(){ int m = 2000; for( int i=1; i<=n; i++ ) ++c[x[i]=a[i]]; for( int i=1; i<=m; i++ ) c[i] += c[i-1]; for( int i=n; i; i-- ) sa[c[x[i]]--] = i; for( int k=1; k<=n; k<<=1 ){ int now = 0; for( int i=n-k+1; i<=n; i++ ) y[++now] = i; for( int i=1; i<=n; i++ ) if(sa[i]>k) y[++now] = sa[i]-k; for( int i=0; i<=m; i++ ) c[i] = 0; for( int i=1; i<=n; i++ ) ++c[x[i]]; for( int i=1; i<=m; i++ ) c[i] += c[i-1]; for( int i=n; i; i-- ) sa[c[x[y[i]]]--] = y[i], y[i]=0; swap(x, y); x[sa[1]] = now = 1; for( int i=2; i<=n; i++ ) x[sa[i]] = (y[sa[i]]==y[sa[i-1]] && y[sa[i]+k]==y[sa[i-1]+k]) ? now : ++now; if( now>=n ) return ; m = now; } } inline void get_height(){ int k = 0; for( int i=1; i<=n; i++ ) rk[sa[i]] = i; for( int i=1; i<=n; i++ ){ if( rk[i]==1 ) continue; if( k ) k--; int j = sa[rk[i]-1]; while( j+k<=n && i+k<=n && a[j+k]==a[i+k] ) k++; height[rk[i]] = k; } } inline bool check1( int l, int r ){ if( r-l+1<t ) return 0; //!!!!!!!r-l+1<t 不是r-l+1<n int tmp = 0; memset( vis, 0, sizeof(vis) ); for( int i=l; i<=r; i++ ) if( !vis[b[sa[i]]] ){ tmp ++; vis[b[sa[i]]] = 1; } return tmp == t; } inline bool check( int x ){ int l=1, r=1; while( l<=n ){ while( height[r+1]>=x ) r++; if( check1(l, r) ) return 1; l = r+1; r = l; } return 0; } int main(){ freopen("in.txt", "r", stdin); scanf("%d", &t); int len = 0; for( int i=1; i<=t; i++ ){ scanf("%d", &M); for( int j=0; j<M; j++ ) scanf("%d", &num[j]); for( int j=1; j<M; j++ ){ a[++n] = num[j]-num[j-1]; //进行差分 b[n] = i; } a[++n] = 0; //分隔不同的串 b[n] = i; } // for( int i=1; i<=n; i++ ) cout << a[i] << endl; get_sa(); get_height(); // for( int i=1; i<=n; i++ ) cout << height[i] <<endl; int l=1, r=n; int ans=0; while( l<=r ){ int mid = l+r>>1; if( check(mid) ) ans = mid, l=mid+1; else r = mid-1; } printf("%d\n", ans+1); return 0; }