JZOJ3975. 【NOI2015模拟1.17】串

题目大意

给定两个整数 n,k ,以及 n 个字符串,求每个字符串中有多少个子串在至少k个字符串(包括其本身)中出现过。

l表示所有的串的总和的长度。
对于10%的数据, n,k100
对于30%的数据, n,k,l1000
对于100%的数据, n,k,l100000

题解

这题比较容易就能想到SA,而对于多个字符串的问题,SA通常是将多个串合并为一个,并在他们中间插入一些没有出现过的字符,将每个串分开。再看这题,对于一个串,他能贡献 l 个后缀(l为字符串长度),如果他的一个后缀的前缀至少出现在了 k 个串中,那么这个前缀能贡献其长度那么多个子串,对于一整个串来说,它的Ans就是所有后缀的 最长 出现 k 次 的前缀长度 之和。
所以我们现在就有了一个直观的想法,先合并所有的串,做一遍SA,然后,按照SA枚举一个区间的右端点,保证这个区间包含k个不同的字符串,这个区间的贡献就是min{Height[l+1~r]} ,然后用线段树维护。每次右端点变化之后都调整左端点,保证区间仍然有 k 个字符串。最后单点查询求出答案。
时间复杂度O(nlogn).

SRC

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std ;

#define N 200000 + 10
const int MX = 20 ;

char str[N] ;
int T[4*N] ;
int SA[N] , Rank[N] , Height[N] ;
int tax[N] , tp[N] ;
int s[N] , f[N][25] , tot[N] , ans[N] , from[N] ;
int n , m = 255 , Q , k , ret ;

bool cmp( int x , int y , int w ) { return tp[x] == tp[y] && tp[x+w] == tp[y+w] ; }

void Rsort() {
    memset( tax , 0 , sizeof(tax) ) ;
    for (int i = 1 ; i <= n ; i ++ ) tax[Rank[tp[i]]] ++ ;
    for (int i = 1 ; i <= m ; i ++ ) tax[i] += tax[i-1] ;
    for (int i = n ; i >= 1 ; i -- ) SA[tax[Rank[tp[i]]]--] = tp[i] ;
}

void Suffix() {
    for (int i = 1 ; i <= n ; i ++ ) Rank[i] = s[i] , tp[i] = i ;
    Rsort() ;
    for (int w = 1 , p = 1 ; p < n ; w += w , m = p ) {
        p = 0 ;
        for (int i = n - w + 1 ; i <= n ; i ++ ) tp[++p] = i ;
        for (int i = 1 ; i <= n ; i ++ ) if ( SA[i] > w ) tp[++p] = SA[i] - w ;
        Rsort() ;
        swap( Rank , tp ) ;
        Rank[SA[1]] = p = 1 ;
        for (int i = 2 ; i <= n ; i ++ ) {
            if ( !cmp( SA[i] , SA[i-1] , w ) ) p ++ ;
            Rank[SA[i]] = p ;
        }
    }
    for (int i = 1 , k = 0 ; i <= n ; i ++ ) {
        if ( k ) k -- ;
        int j = SA[Rank[i] - 1] ;
        while ( s[i+k] == s[j+k] ) k ++ ;
        Height[Rank[i]] = k ;
    }
}

int find( int l , int r ) {
    int j = log(r - l + 1) / log(2) ;
    return min( f[l][j] , f[r-(1 << j)+1][j] ) ;
}

void RMQ() {
    for (int i = 1 ; i <= n ; i ++ ) f[i][0] = Height[i] ;
    for (int j = 1 ; j <= MX ; j ++ ) {
        for (int i = 1 ; i <= n ; i ++ ) {
            if ( i + (1 << (j - 1)) > n ) f[i][j] = f[i][j-1] ;
            else f[i][j] = min( f[i+(1 << (j-1))][j-1] , f[i][j-1] ) ;
        }
    }
}

void Change( int v , int l , int r , int x , int y , int c ) {
    if ( l == x && r == y ) {
        T[v] = max( T[v] , c ) ;
        return ;
    }
    int mid = (l + r) / 2 ;
    if ( y <= mid ) Change( v + v , l , mid , x , y , c ) ;
    else if ( x > mid ) Change( v + v + 1 , mid + 1 , r , x , y , c ) ;
    else {
        Change( v + v , l , mid , x , mid , c ) ;
        Change( v + v + 1 , mid + 1 , r , mid + 1 , y , c ) ;
    }
}

void Search( int v , int l , int r , int x ) {
    if ( l == x && r == x ) {
        ret = max( ret , T[v] ) ;
        return ;
    }
    int mid = (l + r) / 2 ;
    ret = max( ret , T[v] ) ;
    if ( x <= mid ) Search( v + v , l , mid , x ) ;
    else Search( v + v + 1 , mid + 1 , r , x ) ;
}

int main() {
    scanf( "%d%d" , &Q , &k ) ;
    int last = 255 ;
    for (int i = 1 ; i <= Q ; i ++ ) {
        scanf( "%s" , str + 1 ) ;
        int l = strlen( str + 1 ) ;
        if ( k == 1 ) ans[i] = l * (l + 1) / 2 ;
        if ( last == 'a' - 1 ) last = 'z' ;
        if ( n ) s[++n] = ++ last ;
        for (int j = 1 ; j <= l ; j ++ ) s[++n] = str[j] , from[n] = i ;
    }
    if ( k == 1 ) {
        for (int i = 1 ; i <= Q ; i ++ ) printf( "%d " , ans[i] ) ;
        return 0 ;
    }
    m = max( m , last ) ;
    Suffix() ;
    RMQ() ;
    int l = 1 ;
    for (int r = 1 ; r <= n ; r ++ ) {
        if ( from[SA[r]] ) tot[from[SA[r]]] ++ ;
        if ( from[SA[r]] && tot[from[SA[r]]] == 1 ) tot[0] ++ ;
        if ( tot[0] < k ) continue ;
        while ( l < r && tot[0] > k ) {
            tot[from[SA[l]]] -- ;
            if ( !tot[from[SA[l]]] ) tot[0] -- ;
            l ++ ;
        }
        while ( l < r && tot[0] == k ) {
            if ( tot[0] == k ) Change( 1 , 1 , n , l , r , find( l + 1 , r ) ) ;
            tot[from[SA[l]]] -- ;
            if ( !tot[from[SA[l]]] ) { tot[from[SA[l]]] ++ ; break ; }
            l ++ ;
        }
        if ( tot[0] == k ) Change( 1 , 1 , n , l , r , find( l + 1 , r ) ) ;
    }
    for (int i = 1 ; i <= n ; i ++ ) {
        ret = 0 ;
        Search( 1 , 1 , n , i ) ;
        ans[from[SA[i]]] += ret ;
    }
    for (int i = 1 ; i <= Q ; i ++ ) printf( "%d " , ans[i] ) ;
    return 0 ;
}

以上.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值