[BZOJ4199]-[Noi2015]品酒大会-后缀自动姬上的统计问题

说在前面

这真是me遇见过的,近年来最简单的NOI题…


题目

BZOJ4199传送门
UOJ131传送门
洛谷P2178传送门
题面就不粘了,反正哪儿都可以看


解法

根据题目,如果两个位置向后延伸r长度 的字符串都相同,那么就会对0到r的答案产生贡献
也就是要统计各个长度时,子串相等的情况
不难想到后缀自动姬(因为后缀自动姬的right集合是从后向前延伸,并且parent树上的节点代表的就是相同子串的集合)
于是对反串建出后缀自动姬,并考虑在parent树上搞事

对于第一小问,每个parent节点对它的长度len新增的贡献,就是各个子树的叶子节点个数乘积。一开始把主链上的cnt设为1,然后逆拓扑序走一遍就可以统计,最后累加上来(ways[len]+=ways[len+1])
对于第二小问,在每个节点维护最大值,次大值,最小值,次小值,然后逆拓扑序走的时候顺便更新答案就好。me写的比较丑,特判一大坨…


下面是自带大常数的代码

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;

const int inf = 0x3f3f3f3f ;
char ss[300005] ;
int N , id_c , a[300005] , ttt ;
long long ways[300005] , ans[300005] ;
struct Node{
    int len , cnt ;
    int max1 , max2 , min1 , min2 ;
    Node *ch[26] , *par ;
}*root , *las , w[600005] , *tw = w ;

void newNode( Node *&nd , int len ){
    nd = ++ tw ; ++id_c ;
    nd->max1 = nd->max2 = -inf ;
    nd->min1 = nd->min2 = inf ;
    nd->len = len ;
}

void Insert( int id ){
    Node *nd , *tmp = las ;
    newNode( nd , tmp->len + 1 ) ; nd->cnt = 1 ;
    if( a[ttt] > 0 ) nd->max1 = a[ttt] ;
    else nd->min1 = a[ttt] ;
    ttt -- ;
//  printf( "%p\n" , nd ) ; 

    for( ; tmp && !tmp->ch[id] ; tmp = tmp->par )
        tmp->ch[id] = nd ;
    if( !tmp ) nd->par = root ;
    else{
        Node *B = tmp->ch[id] ;
        if( B->len == tmp->len + 1 ) nd->par = B ;
        else{
            Node *nB ; newNode( nB , tmp->len + 1 ) ;
            nB->par = B->par ;
            B->par = nd->par = nB ;
            memcpy( nB->ch , B->ch , sizeof( nB->ch ) ) ;
            while( tmp && tmp->ch[id] == B )
                tmp->ch[id] = nB , tmp = tmp->par ;
        }
    } las = nd ;
}

int sa[600005] , buc[300005] ;
void Rsort(){
    for( int i = 1 ; i <= id_c ; i ++ ) buc[ w[i].len ] ++ ;
    for( int i = 1 ; i <= N ; i ++ ) buc[i] += buc[i-1] ;
    for( int i = id_c ; i ; i -- ) sa[ buc[w[i].len]-- ] = i ;
}

void solve(){
    for( int i = id_c ; i >= 1 ; i -- ){
        Node *nd = &w[ sa[i] ] , *par = nd->par ;

        long long &ans = ::ans[ nd->len ] ;
        if( nd->max2 != -inf )
            ans = max( ans , 1LL * nd->max1 * nd->max2 ) ;
        if( nd->min2 != inf )
            ans = max( ans , 1LL * nd->min1 * nd->min2 ) ;
        if( ans == - ( 1LL << 60 ) && nd->min1 != inf && nd->max1 != -inf )
            ans = max( ans , 1LL * nd->min1 * nd->max1 ) ;
    //  if( nd->len == N - 1 ) printf( "spe :: %lld\n" , ans ) ;

    //  printf( "%p par(%p):%d %d %d %d\n" , nd , par , nd->max1 , nd->max2 , nd->min1 , nd->min2 ) ;
        if( !par ) continue ;
        ways[ par->len ] += 1LL * par->cnt * nd->cnt ;
        par->cnt += nd->cnt ;

        if( par->max1 <= nd->max1 ){
            par->max2 = max( par->max1 , nd->max2 ) ;
            par->max1 = nd->max1 ;
        } else if( nd->max1 > par->max2 )
            par->max2 = nd->max1 ;
        if( nd->max2 > par->max2 )
            par->max2 = nd->max2 ;

        if( par->min1 >= nd->min1 ){
            par->min2 = min( par->min1 , nd->min2 ) ;
            par->min1 = nd->min1 ;
        } else if( nd->min1 < par->min2 )
            par->min2 = nd->min1 ;
        if( nd->min2 < par->min2 )
            par->min2 = nd->min2 ;

    }
    for( int i = N ; i >= 0 ; i -- ){
        ways[i] += ways[i+1] ;
        ans[i] = max( ans[i] , ans[i+1] ) ;
    }
    for( int i = 0 ; i < N ; i ++ )
        printf( "%lld %lld\n" , ways[i] , ways[i] == 0 ? 0LL : ans[i] ) ;
}

int main(){
    newNode( root , 0 ) ; root->par = NULL ;
    las = root ;

    scanf( "%d%s" , &N , ss ) ;
    for( int i = 0 ; i < N ; i ++ ){
        scanf( "%d" , &a[i] ) ;
    } ttt = N - 1 ;
    for( int i = N + 1 ; i >= 0 ; i -- )
        ans[i] = - ( 1LL << 60 ) ;
    for( int i = N - 1 ; i >= 0 ; i -- )
        Insert( ss[i] - 'a' ) ;
    Rsort() ; solve() ;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值