hdu 4878 ZCC loves words(AC自动机+dp+矩阵快速幂+中国剩余定理)

hdu 4878 ZCC loves words(AC自动机+dp+矩阵快速幂+中国剩余定理)

题意:给出若干个模式串,总长度不超过40,对于某一个字符串,它有一个价值,对于这个价值的计算方法是这样的,设初始价值为V=1,假如这个串能匹配第k个模式串,则V=V*prime[k]*(i+len[k]),其中prime[k]表示第k个素数,i表示匹配的结束位置,len[k]表示第k个模式串的长度(注意,一个字符串可以多次匹配同意个模式串)。问字符集为‘A‘-‘Z‘的字符,组成的所有的长为L的字符串,能得到的总价值和是多少?

解法:跟以前做过的很多AC自动机的题有点类似,很容易想到一个node*L的dp,dp[i][v]表示长为i,匹配到AC自动机的V节点能得到的价值和(详见代码中的DEBUG函数)。但是L太大,没法搞。节点总数只有40,那么就可以用矩阵来加速dp了,但是很可惜,建立矩阵的时候,发现建的矩阵居然是跟i有关,这样是不能直接用矩阵快速幂做的。但是,题目给出的提示是,mod可以拆成三个较小的质数。那么我们可以分别用三个较小的质数作为mod进行运算,因为第i个矩阵,它是跟第i+mod个矩阵一样的,所以我们可以把L个矩阵分成L/mod段,每一段的矩阵乘起来都是一样的,设其为A(可以暴力乘起来,因为mod很小),那么我们要的所有的L个矩阵的乘起来得到的矩阵,就是A^(L/mod),再乘上剩下来多余的L%mod个了,这样就可以计算出在每个较小的模系下的答案。最后用中国剩余定理计算总的答案。

代码:

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<vector>
#include<queue>
#define ll __int64
using namespace std ;

const int N = 44 ;
const int mod = 5047621 ;
int pri[12345] , p_num , vis[12345] ;

void get_prime () {
    p_num = 0 ;
    for ( int i = 2 ; i < 12345 ; i ++ ) {
        if ( !vis[i] ) pri[++p_num] = i ;
        for ( int j = 1 ; j <= p_num ; j ++ ) {
            if ( i * pri[j] >= 12345 ) break ;
            vis[i*pri[j]] = 1 ;
            if ( i % pri[j] == 0 ) break ;
        }
    }
}

struct Point {
    int len , p ;
    Point () {}
    Point ( int a , int b ):len(a),p(b) {}
} ;
struct RECT {
    int elem[N][N] ;
    void print ( int n ) {
        for ( int i = 0 ; i < n ; i ++ , puts ( "" ) )
            for ( int j = 0 ; j < n ; j ++ )
                printf ( "%d " , elem[i][j] ) ;
    }
} p[222] , E ;
struct AC_auto {
    int dp[111][44] ;
    int c[N][26] , fail[N] , tot ;
    vector<Point> vec[N] ;
    queue<int> Q ;
    void init () {
        tot = 0 ;
        new_node () ;
    }
    int new_node () {
        vec[tot].clear () ;
        fail[tot] = 0 ;
        memset ( c[tot] , 0 , sizeof ( c[tot] ) ) ;
        return tot ++ ;
    }
    void insert ( char *s , int i ) {
        int now = 0 , len = strlen ( s ) ;
        for ( ; *s ; s ++ ) {
            int k = *s - 'A' ;
            if ( !c[now][k] ) c[now][k] = new_node () ;
            now = c[now][k] ;
        }
        vec[now].push_back ( Point ( len , pri[i] ) ) ;
    }
    void get_fail () {
        int u = 0 , v ;
        for ( int i = 0 ; i < 26 ; i ++ ) {
            if ( c[u][i] )
                Q.push ( c[u][i] ) ;
        }
        while ( !Q.empty () ) {
            u = Q.front () ; Q.pop () ;
            for ( int i = 0 ; i < 26 ; i ++ ) {
                if ( c[u][i] ) {
                    v = c[u][i] ;
                    fail[v] = c[fail[u]][i] ;
                    Q.push ( v ) ;
                } else c[u][i] = c[fail[u]][i] ;
            }
        }
    }
    void BUILD_RECT ( int l , int mod ) {
        memset ( p[l].elem , 0 , sizeof ( p[l].elem ) ) ;
        for ( int i = 0 ; i < tot ; i ++ ) {
            for ( int j = 0 ; j < 26 ; j ++ ) {
                int u = c[i][j] ;
                int v = u , ret = 1 ;
                while ( v ) {
                    for ( int k = 0 ; k < vec[v].size () ; k ++ ) {
                        Point u = vec[v][k] ;
                        ret *= (l+u.len)*u.p ;
                        ret %= mod ;
                    }
                    v = fail[v] ;
                }
                p[l].elem[u][i] += ret ;
                if ( p[l].elem[u][i] >= mod )
                    p[l].elem[u][i] -= mod ;
            }
        }
    }
    void RECT_MUIL ( RECT x , RECT y , RECT &z , int mod ) {
        memset ( z.elem , 0 , sizeof ( z.elem ) ) ;
        for ( int i = 0 ; i < tot ; i ++ ) {
            for ( int j = 0 ; j < tot ; j ++ )
                for ( int k = 0 ; k < tot ; k ++ ) {
                    z.elem[i][j] += x.elem[i][k] * y.elem[k][j] % mod ;
                    if ( z.elem[i][j] >= mod )
                        z.elem[i][j] -= mod ;
                }
        }
    }
    void GAO ( RECT& ret , ll n , int mod ) {
  //      printf ( "n = %I64d\n" , n ) ;
        RECT f = ret ; ret = E ;
        while ( n ) {
            if ( n & 1 ) RECT_MUIL ( ret , f , ret , mod ) ;
            RECT_MUIL ( f , f , f , mod ) ;
            n >>= 1 ;
        }
    }
    int SOLVE ( int mod , ll l ) {
        RECT ans = E , temp = E ;
    //    printf ( "mod = %d\n" , mod ) ;
        for ( int i = mod ; i >= 1 ; i -- ) {
            BUILD_RECT ( i , mod ) ;
            RECT_MUIL ( temp , p[i] , temp , mod ) ;
        //    if (i == 1) ans.print ( tot ) ;
        }
   //     puts( "fuck ") ;
        GAO ( temp , l/mod , mod ) ;
   //     ans.print ( tot ) ;
        for ( int i = l % mod ; i >= 1 ; i -- ) {
            BUILD_RECT ( i , mod ) ;
            RECT_MUIL ( ans , p[i] , ans , mod ) ;
        }
        RECT_MUIL ( ans , temp , ans , mod ) ;
    //    ans.print ( tot ) ;
        int ret = 0 ;
        for ( int i = 0 ; i < tot ; i ++ ) {
            ret += ans.elem[i][0] ;
            if ( ret >= mod ) ret -= mod ;
        }
        return ret ;
    }
    void DEBUG ( ll l ) {
        memset ( dp , 0 , sizeof ( dp ) ) ;
        dp[0][0] = 1 ;
        for ( int i = 0 ; i < l ; i ++ ) {
            for ( int j = 0 ; j < tot ; j ++ ) {
                for ( int k = 0 ; k < 26 ; k ++ ) {
                    int u = c[j][k] ;
                    int v = u ;
                    int ret = 1 ;
                    while ( v ) {
                        for ( int g = 0 ; g < vec[v].size () ; g ++ ) {
                            Point f = vec[v][g] ;
                            ret *= (i+1+f.len) * f.p ;
                        }
                        v = fail[v] ;
                    }
                    dp[i+1][u] += dp[i][j] * ret % mod ;
                    if ( dp[i+1][u] >= mod ) dp[i+1][u] -= mod ;
                }
            }
        }
        int ans = 0 ;
        for ( int i = 0 ; i < tot ; i ++ ) {
            ans += dp[l][i] ;
            if ( ans >= mod ) ans -= mod ;
        }
        puts ( "fuck" ) ;
        printf ( "%d\n" , ans ) ;
    }
} ac ;

void extend_gcd ( ll a , ll b , int &x , int &y ) {
    if ( !b ) x = 1 , y = 0 ;
    else extend_gcd ( b , a % b , y , x ) , y -= x * ( a / b ) ;
}

char s[1111] ;
int main () {
    for ( int i = 0 ; i < N ; i ++ )
        for ( int j = 0 ; j < N ; j ++ )
            E.elem[i][j] = i == j ;
    get_prime () ;
    int n ; ll l ;
    int ca = 0 ;
    while ( scanf ( "%d%I64d" , &n , &l ) != EOF ) {
        ac.init () ;
        for ( int i = 1 ; i <= n ; i ++ ) {
            scanf ( "%s" , s ) ;
            ac.insert ( s , i ) ;
        }
        ac.get_fail () ;
    //    ac.DEBUG ( l ) ;
        int m1 , mm1 , m2 , mm2 , m3 , mm3 , fuck ;//mm为m的乘法逆元
        m1 = 173 * 179 , m2 = 163 * 179 , m3 = 163 * 173 ;
        extend_gcd ( m1 , 163 , mm1 , fuck ) ;
        extend_gcd ( m2 , 173 , mm2 , fuck ) ;
        extend_gcd ( m3 , 179 , mm3 , fuck ) ;
        int a1 = ac.SOLVE ( 163 , l ) ;
    //    printf( "a1 = %d\n"  , a1 ) ;
        int a2 = ac.SOLVE ( 173 , l ) ;
  //      printf ( "a2 = %d\n" , a2 ) ;
        int a3 = ac.SOLVE ( 179 , l ) ;
 //       printf ( "a3 = %d\n" , a3 ) ;
        int ans = ( a1 * m1 * mm1 + a2 * m2 * mm2 + a3 * m3 * mm3 ) % 5047621 ;
        printf ( "Case #%d: %d\n" , ++ ca , ans ) ;
    }
    return 0 ;
}
/*
2 3
AB
BB
2 2
A
B
*/

hdu 4878 ZCC loves words(AC自动机+dp+矩阵快速幂+中国剩余定理)

时间: 2024-12-27 16:52:19

hdu 4878 ZCC loves words(AC自动机+dp+矩阵快速幂+中国剩余定理)的相关文章

HDU 5434 Peace small elephant 状压dp+矩阵快速幂

题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=5434 Peace small elephant Accepts: 38 Submissions: 108 Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others) 问题描述 小明很喜欢国际象棋,尤其喜欢国际象棋里面的大象(只要无阻挡能够斜着走任意格),但是他觉得国际象棋里的大象太凶残了,于是他

HDU 3341 Lost&#39;s revenge AC自动机+dp

Lost's revenge Time Limit: 15000/5000 MS (Java/Others)    Memory Limit: 65535/65535 K (Java/Others)Total Submission(s): 3757    Accepted Submission(s): 1020 Problem Description Lost and AekdyCoin are friends. They always play "number game"(A bor

Hdu 3341 Lost&#39;s revenge (ac自动机+dp+hash)

题目大意: 给出很多个DNA串,每一个串的价值为1,最后给出一个长串,要你重新排列最后的串使之它所有的子串的权值和最大. 思路分析: 最先容易想到的思路就是搜!管她3721..直接一个字符一个字符的码,然后在AC自动机上判断最后的权值.TLE哟. 然后发现搜过不去,那就dp咯.再容易想到的就是dp[i][a][b][c][d] 表示此时遍历AC自动机的节点在i,然后构成了a个A,b个G,c个C,d个T的权值. 再一看内存,500*40*40*40*40...然后...就没有然后了 再想,因为它说

HDU 2294 Pendant (DP+矩阵快速幂降维)

HDU 2294 Pendant (DP+矩阵快速幂降维) ACM 题目地址:HDU 2294 Pendant 题意: 土豪给妹子做首饰,他有K种珍珠,每种N个,为了炫富,他每种珍珠都要用上.问他能做几种长度[1,N]的首饰. 分析: 1 ≤ N ≤ 1,000,000,000简直可怕. 首先想dp,很明显可以想到: dp[i][j] = (k-(j-1))*dp[i-1][j-1] + j*dp[i-1][j](dp[i][j]表示长度为i的并且有j种珍珠的垂饰有多少个) 然后遇到N太大的话,

HDU5863 cjj&#39;s string game(DP + 矩阵快速幂)

题目 Source http://acm.split.hdu.edu.cn/showproblem.php?pid=5863 Description cjj has k kinds of characters the number of which are infinite. He wants to build two strings with the characters. The lengths of the strings are both equal to n. cjj also def

POJ3735 Training little cats DP,矩阵快速幂,稀疏矩阵优化

题目大意是,n只猫,有k个动作让它们去完成,并且重复m次,动作主要有三类gi,ei,s i j,分别代表第i只猫获得一个花生,第i只猫吃掉它自己所有的花生,第i只和第j只猫交换彼此的花生.k,n不超过100,m不超过1000,000,000,计算出最后每只猫还剩下多少个花生. 我们假设一个n维向量P,每个分量的值代表这n只猫所拥有的花生数,那么对于gi操作其实就是在第i维分量上加上1:对于ei,那就是在第i维分量上乘以0,说到这里,有木有感觉这很像3D坐标转化中的平移矩阵和缩放矩阵?没错,就是这

POJ3420 Quad Tiling DP + 矩阵快速幂

题目大意是用1*2的骨牌堆积成4*N的矩形,一共有多少种方法,N不超过10^9. 这题和曾经在庞果网上做过的一道木块砌墙几乎一样.因为骨牌我们可以横着放,竖着放,我们假设以4为列,N为行这样去看,并且在骨牌覆盖的位置上置1,所以一共最多有16种状态.我们在第M行放骨牌的时候,第M+1行的状态也是有可能被改变的,设S(i,j)表示某一行状态为i时,将其铺满后下一行状态为j的方案书.考虑下如果我们让矩阵S和S相乘会有什么意义,考虑一下会发现S*S的意义当某行状态为i,接着其后面第2行的状态为j的可行

UVA 11651 - Krypton Number System(DP+矩阵快速幂)

UVA 11651 - Krypton Number System 题目链接 题意:给一个进制base,一个分数score求该进制下,有多少数满足一下条件: 1.没有连续数字 2.没有前导零 3.分数为score,分数的计算方式为相邻数字的平方差的和 思路:先从dp入手,dp[i][j]表示组成i,最后一个数字为j的种数,然后进行状态转移,推出前面一步能构成的状态,也就是到dp[(b - 1) * (b - 1)][x]. 然后可以发现后面的状态,都可以由前面这些状态统一转移出来,这样就可以利用

HDU 4549 M斐波那契数列 ( 矩阵快速幂 + 费马小定理 )

HDU 4549 M斐波那契数列 (  矩阵快速幂 + 费马小定理  ) 题意:中文题,不解释 分析:最好的分析就是先推一推前几项,看看有什么规律 #include <cstdio> #include <cstring> #include <algorithm> using namespace std; typedef __int64 LL; #define CLR( a, b ) memset( a, b, sizeof(a) ) #define MOD 100000