题目大意:通常押韵的两个词以相同的字符结尾。我们运用这个特性来规定反押韵的概念。反押韵是一对拥有近似开头的单词。一对单词的反押韵的复杂度被定义为两者都以之开头且最长的字符串S的长度。因此,"arboreal" 和"arturus"是复杂度为2的一对反押韵,且"chalkboard"和"overboard"是一对复杂度为0的反押韵。你将得到一列单词。你的任务是,得到一列(i,j)形式的问题后,输出列表上以第i个和第j个组成的一对字符串的反押韵的复杂度。
字符串个数与询问的个数还有字符串的长度都在输入中说明。有字符串的长度*询问的数量得到最坏情况下一个case的复杂度为O(L*Q). 其中L和Q的数据范围确定了暴力肯定会超时!
然后后缀数组的性质可以通过后缀数组确定最长公共前缀(LCP)的长度。利用倍增法求后缀数组只需要O(nlogn)的时间复杂度。然后每次确定两个串之间LCP只需要求以两个目标串为钱缀的后缀之间求height的最小值即可。但是如果每次求最小值都需要O(N)的复杂度任然会超时。可以利用RMQ算法先用O(nlogn)的复杂度预先处理下height数组。再利用RMQ的O(1)的复杂度求区间最值即可。 该题给n个字符串,我们可以将n个字符串拼接成一个长串求后缀数组后即可利用后缀数组确定height数组。然后便是RMQ问题。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 4000010;
const int maxlog = 23;
struct SuffixArray
{
int s[maxn]; // 原始字符数组(最后一个字符应必须是0,而前面的字符必须非0)
int sa[maxn]; // 后缀数组
int rank[maxn]; // 名次数组. rank[0]一定是n-1,即最后一个字符,rank从0开始
int height[maxn]; // height数组 代表sa[i-1] 和 sa[i]的LCP的长度
int t[maxn], t2[maxn], c[maxn]; // 辅助数组
int n; // 字符个数
void clear()
{
n = 0;
memset(sa, 0, sizeof(sa));
}
// m为最大字符值加1。调用之前需设置好s和n
void build_sa(int m)
{
int i, *x = t, *y = t2;
for(i = 0; i < m; i++) c[i] = 0;
for(i = 0; i < n; i++) c[x[i] = s[i]]++;
for(i = 1; i < m; i++) c[i] += c[i-1];
for(i = n-1; i >= 0; i--) sa[--c[x[i]]] = i;
for(int k = 1; k <= n; k <<= 1)
{
int p = 0;
for(i = n-k; i < n; i++) y[p++] = i;
for(i = 0; i < n; i++) if(sa[i] >= k) y[p++] = sa[i]-k;
for(i = 0; i < m; i++) c[i] = 0;
for(i = 0; i < n; i++) c[x[y[i]]]++;
for(i = 0; i < m; i++) c[i] += c[i-1];
for(i = n-1; i >= 0; i--) sa[--c[x[y[i]]]] = y[i];
swap(x, y);
p = 1;
x[sa[0]] = 0;
for(i = 1; i < n; i++)
x[sa[i]] = y[sa[i-1]]==y[sa[i]] && y[sa[i-1]+k]==y[sa[i]+k] ? p-1 : p++;
if(p >= n) break;
m = p;
}
}
void build_height()
{
int i, j, k = 0;
for(i = 0; i < n; i++) rank[sa[i]] = i;
for(i = 1; i < n; i++)
{
if(k) k--;
int j = sa[rank[i]-1];
while(s[i+k] == s[j+k]) k++;
height[rank[i]] = k;
}
}
};
struct RMQ //用于求区间最值
{
int d[maxn][maxlog];
void init(const int * A, int n) //被求最值的数组,数组的大小
{
//int n = A.size();
for(int i = 0; i < n; i++)
d[i][0] = A[i];
for(int j = 1; (1<<j) <= n; j++)
for(int i = 0; i + (1<<j) - 1 < n; i++)
d[i][j] = min(d[i][j-1], d[i + (1<<(j-1))][j-1]);
}
int query(int L, int R)
{
if(R < L) swap(L, R);
L++;
int k = 0;
while((1<<(k+1)) <= R-L+1) k++; // 如果2^(k+1)<=R-L+1,那么k还可以加1
return min(d[L][k], d[R-(1<<k)+1][k]);
}
};
RMQ rmq;
SuffixArray sa;
int pos[maxn], len[maxn];
char t[maxn];
int main()
{
//freopen("cin.txt","r",stdin);
int T,ca=1,n,m;
scanf("%d\n",&T);
while(T--)
{
printf("Case %d:\n",ca++ );
scanf("%d",&n);
int u=1;
for(int i=1;i<=n;i++)
{
scanf("%s",t);
len[i]=strlen(t);
pos[i]=u; //每个字符串开头的位置
for(int j=0;j<len[i];j++) sa.s[u++]=t[j];
}
sa.s[u]=0;
sa.n=u;
sa.build_sa(256); //ascii码最大值为255
sa.build_height();
rmq.init(sa.height,u);
scanf("%d",&m);
int x,y;
int ans;
for(int i=0;i<m;i++)
{
scanf("%d%d",&x,&y);
ans=min(len[x],len[y]);
if(x==y)
printf("%d\n",ans);
else
{
ans=min(ans,rmq.query(sa.rank[pos[x]],sa.rank[pos[y]]));
printf("%d\n",ans);
}
}
}
}
rmq算法模板 o(n*log(n)) 预处理 o(1)查询
struct RMQ //用于求区间最值
{
int d[maxn][maxlog];
void init(const int * A, int n) //被求最值的数组,数组的大小
{
//int n = A.size();
for(int i = 0; i < n; i++)
d[i][0] = A[i];
for(int j = 1; (1<<j) <= n; j++)
for(int i = 0; i + (1<<j) - 1 < n; i++)
d[i][j] = max(d[i][j-1], d[i + (1<<(j-1))][j-1]);//最小值改为min
}
int query(int L, int R)
{
if(R < L) swap(L, R);
//L++; //根据rank数组下标而定,该题rank[0]=n-1,是自己设的表示末尾的字符
int k = 0;
while((1<<(k+1)) <= R-L+1) k++;
return max(d[L][k], d[R-(1<<k)+1][k]);//最小值改为min
}
};
RMQ rmq;
后缀数组算法模板o(n*logn)
struct SuffixArray
{
int s[maxn]; // 原始字符数组(最后一个字符应必须是0,而前面的字符必须非0)
int sa[maxn]; // 后缀数组(从第i个字符开始的字符串是第几名)
int rank[maxn]; // 名次数组. rank[0]一定是n-1,即最后一个字符,rank从0开始
int height[maxn]; // height数组 代表sa[i-1] 和 sa[i]的LCP的长度
int t[maxn], t2[maxn], c[maxn]; // 辅助数组
int n; // 字符个数
void clear()
{
n = 0;
memset(sa, 0, sizeof(sa));
}
// m为最大字符值加1。调用之前需设置好s和n
void build_sa(int m)
{
int i, *x = t, *y = t2;
for(i = 0; i < m; i++) c[i] = 0;
for(i = 0; i < n; i++) c[x[i] = s[i]]++;
for(i = 1; i < m; i++) c[i] += c[i-1];
for(i = n-1; i >= 0; i--) sa[--c[x[i]]] = i;
for(int k = 1; k <= n; k <<= 1)
{
int p = 0;
for(i = n-k; i < n; i++) y[p++] = i;
for(i = 0; i < n; i++) if(sa[i] >= k) y[p++] = sa[i]-k;
for(i = 0; i < m; i++) c[i] = 0;
for(i = 0; i < n; i++) c[x[y[i]]]++;
for(i = 0; i < m; i++) c[i] += c[i-1];
for(i = n-1; i >= 0; i--) sa[--c[x[y[i]]]] = y[i];
swap(x, y);
p = 1;
x[sa[0]] = 0;
for(i = 1; i < n; i++)
x[sa[i]] = y[sa[i-1]]==y[sa[i]] && y[sa[i-1]+k]==y[sa[i]+k] ? p-1 : p++;
if(p >= n) break;
m = p;
}
}
void build_height()//建立height数组,用于求排名相邻的两个后缀的最长公共前缀
{
int i, j, k = 0;
for(i = 0; i < n; i++) rank[sa[i]] = i;
for(i = 1; i < n; i++)
{
if(k) k--;
int j = sa[rank[i]-1];
while(s[i+k] == s[j+k]) k++;
height[rank[i]] = k;
}
}
};