UVALive 5794 (UVA 12361) File Retrieval 后缀数组 + 分治dfs

题目大意:

就是现在给出F(1 <= F <= 60)个字符串, 每个字符串的长度不超过 10^4, 并且每个字符串只包含小写英文字母, 现在有一个搜索装置, 如果对于字符串s进行搜索, 那么所有的F个给定的字符串中包含s作为子串的字符串都被搜索出来, 问呗搜索出来的所有字符串组成的集合有多少种不同的可能


大致思路:

首先不难想到将F个串中间用未出现的不同字符隔开连接起来, 然后处理出后缀数组, 然后我们可以用后缀树的样子来看这些后缀之间的关系(后缀树组作为工具), 不能发现如果某一段连续的极大区间[L, R]中的height值都 >= h(也就是这个区间旁边的 < h),那么我们对于后缀sa[L - 1], sa[L], .... sa[R]的前 <= h个字符进行搜索这几个后缀所在字符串一定会同时出现(从后缀树上的公共部分到一个深度为h的分支, 很容易看出), 那么我们利用height数组进行分治, 用h = 0得到所有连续的 > 0的height区间段, 对于这些段搜索即可, 然后对于>= h的这一连续区间段继续进行分割(取这个区间当中不是h的最小的下一个h进行分割), 这样进行分治, 复杂度O(nlogn), 至于集合的状态用状态压缩表示之后塞到set当中即可, 最后统计set中元素的个数

当然以上从h = 0开始分治时会忽略一种情况, 就是字符串单独出现的情况, 这样的情况只需要利用后缀树组的height数组判断两个后缀的LCP的长度是不是等于串长即可判断子串问题, 成为另外一个字符串的子串的字符串不可能单独出现, 这样所有情况就考虑完毕了


代码如下:

Result  :  Accepted     Memory  :  ? KB     Time  :  812 ms (UVALive), 1502 ms (UVA)

/*
 * Author: Gatevin
 * Created Time:  2015/3/12 16:14:14
 * File Name: Kotori_Itsuka.cpp
 */
#include<iostream>
#include<sstream>
#include<fstream>
#include<vector>
#include<list>
#include<deque>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cmath>
#include<ctime>
#include<iomanip>
using namespace std;
const double eps(1e-8);
typedef long long lint;
#define maxn 600100

int wa[maxn], wb[maxn], wv[maxn], Ws[maxn];
char in[maxn];
int F, N, s[maxn], sa[maxn], belong[maxn], rank[maxn], height[maxn], L[66];
set <lint> S;
bool single[66];

int cmp(int *r, int a, int b, int l)
{
    return r[a] == r[b] && r[a + l] == r[b + l];
}

void da(int *r, int *sa, int n, int m)
{
    int *x = wa, *y = wb, *t, i, j, p;
    for(i = 0; i < m; i++) Ws[i] = 0;
    for(i = 0; i < n; i++) Ws[x[i] = r[i]]++;
    for(i = 1; i < m; i++) Ws[i] += Ws[i - 1];
    for(i = n - 1; i >= 0; i--) sa[--Ws[x[i]]] = i;
    for(j = 1, p = 1; p < n; j *= 2, m = p)
    {
        for(p = 0, i = n - j; i < n; i++) y[p++] = i;
        for(i = 0; i < n; i++) if(sa[i] >= j) y[p++] = sa[i] - j;
        for(i = 0; i < n; i++) wv[i] = x[y[i]];
        for(i = 0; i < m; i++) Ws[i] = 0;
        for(i = 0; i < n; i++) Ws[wv[i]]++;
        for(i = 1; i < m; i++) Ws[i] += Ws[i - 1];
        for(i = n - 1; i >= 0; i--) sa[--Ws[wv[i]]] = y[i];
        for(t = x, x = y, y = t, p = 1, x[sa[0]] = 0, i = 1; i < n; i++)
            x[sa[i]] = cmp(y, sa[i - 1], sa[i], j) ? p - 1 : p++;
    }
    return;
}

void calheight(int *r, int *sa, int n)
{
    int i, j, k = 0;
    for(i = 1; i <= n; i++) rank[sa[i]] = i;
    for(i = 0; i < n; height[rank[i++]] = k)
        for(k ? k-- : 0, j = sa[rank[i] - 1]; r[i + k] == r[j + k]; k++);
    return;
}

/*
 * dfs(L, R, h)寻找的是连续的height[i] >= h的一段height数组 L <= i <= R中的所有解
 * 那么对[L, R]当中的所有串的前不超过h个字符的串进行搜索都将搜索到所有的这[L, R]中的串
 * 统计这种情况下的集合状况插入set之后, 继续对这个区间进行分割, 以大于h的最小的新h进行分割
 * 也就是分治的思想
 * 初始的时候用的h = 0分割, height[i] = 0造成特殊情况是造成集合元素个数为1的没有统计
 * 所以在dfs之前统计一下单个字符串出现的可能
 */
void dfs(int L, int R, int h)
{
    int i = L;
    while(i <= R)
    {
        while(i <= R && height[i] == h) i++;
        if(i > R) break;
        int j = i;
        lint pos = 1LL << belong[sa[i - 1]];
        int pre = height[i];
        while(j <= R && height[j] > h)
            pos |= (1LL << belong[sa[j]]), pre = min(pre, height[j]), j++;
        S.insert(pos);
        dfs(i, j - 1, pre);
        i = j;
    }
    return;
}

void solve()
{
    S.clear();
    memset(single, 0, sizeof(single));
    for(int i = 1; i <= N; i++)//如果一个字符串不是其他串的子串即可单独出现
    {
        if(belong[sa[i - 1]] != -1 && L[belong[sa[i - 1]]] == height[i]) single[belong[sa[i - 1]]] = 1;
        if(belong[sa[i]] != -1 && L[belong[sa[i]]] == height[i]) single[belong[sa[i]]] = 1;
    }
    for(int i = 1; i <= F; i++)
        if(!single[i]) S.insert(1LL << i);
    dfs(1, N, 0);
    printf("%d\n", S.size());
    return;
}

int main()
{
    while(scanf("%d", &F), F)
    {
        N = 0;
        memset(belong, 0, sizeof(belong));
        for(int i = 1; i <= F; i++)
        {
            scanf("%s", in);
            int tlen = strlen(in);
            L[i] = tlen;
            for(int j = 0; j < tlen; j++)
            {
                belong[N] = i;
                s[N++] = in[j] - 'a' + 1;
            }
            belong[N] = -1;
            s[N++] = 26 + i;
        }
        N--;
        s[N] = 0;
        da(s, sa, N + 1, 90);
        calheight(s, sa, N);
        solve();
    }
    return 0;
}


你可能感兴趣的:(File,分治,uvalive,uva,retrieval,5794,12361,后缀树组)