[BZOJ1559]-[JSOI2009]密码-补全AC自动机+状压dp

说在前面

这个题的代码真的恶心…AC自动机上dp就算了,居然还要输出方案= =???(黑人问号脸.jpg)
本来计划着今天把AC自动机(补全)和trie搞了。在一个讲稿里发现了这个题,看的顺眼决定去写一写=w=
然后被这道题折磨致死…吃饭之前就开始写,一直写到21:09才AC


题目

BZOJ1559传送门
原题是图片,就不粘题面了…
这题没有权限,可以进去看


解法

(因为是在一个讲稿里发现的所以…已经知道了大概是AC自动机+dp。)
直接讲做法吧…建出AC自动机,补全,然后跑状压DP。
以下给定串代表输入的串,字母串代表符合题意的答案串。

因为题目是要统计合法的字母串数,而这个答案可能很大。所以要么是数论(或者乘法原理相关)要么是DP
然而稍微一想,就可以把第一个作法给cut掉,因为字符串之间可能相交,排列顺序也很多,并且字符串与字符串之间还可以随意添加字母,用数学方法几乎是不可解的。所以基本咬定DP
给定串总共只有10个,而最后只有全部包含这些给定串的字母串才合法,不难想到可以把「给定串是否在字母串中出现过」压位,也就是状压。看一看数据范围也能确定这个想法(至少有可过的可能性)

不难想到dp数组。定义dp数组dp[i][state][j]表示当前已经选了第i个字母,包含状态为state,在第j个节点上(注意是节点而不是字母,不然无法转移,可以自己想一想)。每个位置都有26种选择,如果当前节点有这个儿子,就直接转移到儿子,如果没有,就需要跳fail进行转移。在实现的时候,为了避免重复跳fail(转移代价太大),选择补全AC自动机。

另外关于为什么dp数组第三维要定义成节点而不是字母,因为在trie上,一个节点代表的是一个字符串的前缀。如果在下一步选了一个当前节点有的儿子,就直接进去。如果选择的是当前没有的儿子,就去找失配,前面已经匹配上的但是在失配之后消失掉的那一截字符串,就相当于是选择了给定串之外的随意字符。所以某个节点和他的fail虽然字母是一样的,但是含义不一样,因此这样定义是不会重复计算的。

关于输出方案,直接根据dp数组倒推回去即可,枚举所有节点,对于可以转移到当前状态的节点就递归下去,细节看代码。


下面是自带大常数的代码

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

bool ignore[15] ;
char ss[15][15] ;
int L , N ;
long long dp[27][1<<11][110] , ans ;
struct Node{
    int id , isend ;
    char now ;
    Node* ch[26] , *fail ;
}w[2005] , *tw = w , *root ;

bool inside( int x , int y ){
    int lenx = strlen( ss[x] ) , leny = strlen( ss[y] ) ;
    if( lenx < leny ) return false ;
    for( int i = 0 , j , tmp ; i < lenx ; i ++ ){
        for( j = 0 , tmp = i ; j < leny && tmp < lenx ; j ++ ,tmp ++ )
            if( ss[x][tmp] != ss[y][j] ) break ;
        if( j == leny ) return true ;
    }
    return false ;
}

void preWork(){
    for( int i = 1 ; i <= N ; i ++ )
        for( int j = 1 ; j <= N ; j ++ ){
            if( i == j ) continue ;
            if( strcmp( ss[i] , ss[j] ) == 0 ) ignore[ max(i,j) ] = true ;
            else if( inside( i , j ) ) ignore[j] = true ;
        }
    int tmp = 0 ;
    for( int i = 1 ; i <= N ; i ++ ){
        if( ignore[i] ) continue ;
        ++ tmp ;
        for( int j = 0 ; j <= 10 ; j ++ )
            ss[tmp][j] = ss[i][j] ;
    }
    N = tmp ;
}

void Insert( Node *nd , char *cc , int Emm ){
    int len = strlen( cc ) ;
    for( int i = 0 ; i < len ; i ++ ){
    //  printf( "i(%d) nd_ads(%d) char(%c)\n" , i , nd , nd->now ) ;
        int id = cc[i] - 'a' ;
        if( !nd->ch[id] ){
            nd->ch[id] = ++tw ;
            nd->ch[id]->now = cc[i] ;
            nd->ch[id]->id = tw - w ;
        }
        nd = nd->ch[id] ;
    }
    nd->isend = Emm ;
}

queue<Node*> que ;
void getFail(){
    que.push( root ) ;
    while( !que.empty() ){
        Node *u = que.front() ; que.pop() ;
        for( int i = 0 ; i < 26 ; i ++ ){
            Node *&v = u->ch[i] ;
            Node *p = u->fail ;
            while( p && !p->ch[i] ) p = p->fail ;
            if( v ){
                v->fail = ( p ? p->ch[i] : root ) ;
                que.push( v ) ;
            } else v = ( p ? p->ch[i] : root ) ;
        }
    }
}

char anstr[45][45] ;
int sta[45] , topp , cntstr , rank[45] ;
void dfs( int left , int state , int nowid , int c ){
    if( left == 0 ){
        //if( state ) return ;
        cntstr ++ ;
        for( int i = 0 ; i < L ; i ++ )
            anstr[cntstr][i] = sta[i] ;
        anstr[cntstr][L] = 0 ;
        return ;
    }
    sta[left-1] = c + 'a' ;
    for( int i = 1 ; i <= tw - w ; i ++ ){
        if( dp[left-1][state][i] && w[i].ch[c]->id == nowid )
            dfs( left - 1 , state , w[i].id , w[i].now - 'a' ) ;
    }
    if( w[nowid].isend ){
        int x = ( 1 << ( w[nowid].isend - 1 ) ) ;
        if( (state&x) == 0 ) return ;
        for( int i = 1 ; i <= tw - w ; i ++ ){
            if( dp[left-1][state^x][i] && w[i].ch[c]->id == nowid )
                dfs( left - 1 , state^x , w[i].id , w[i].now - 'a' ) ;
        }
    }
}

bool cmp( const int &i , const int &j ){
    for( int k = 0 ; k < L ; k ++ )
        if( anstr[i][k] != anstr[j][k] )
            return anstr[i][k] < anstr[j][k] ;
    return false ;
}

void solve(){
    root = ++tw ; root->id = 1 ;
    for( int i = 1 ; i <= N ; i ++ )
        Insert( root , ss[i] , i ) ;
    getFail() ;

    int Fstate = ( 1 << N ) - 1 , totnd = tw - w ;
    dp[0][0][1] = 1 ;
    for( int i = 0 ; i <= L ; i ++ ){
        for( int S = 0 ; S <= Fstate ; S ++ ){
            for( int k = 1 ; k <= totnd ; k ++ ){
                if( !dp[i][S][k] ) continue ;
                for( int f = 0 , news , id ; f < 26 ; f ++ ){
                        if( w[k].ch[f]->isend )
                        news = ( 1 << ( w[k].ch[f]->isend - 1 ) ) ;
                    else news = 0 ;
                    id = w[k].ch[f]->id ;
                    dp[i+1][S|news][id] += dp[i][S][k] ;
                }
            }
        }
    }
    for( int i = 1 ; i <= totnd ; i ++ )
        ans += dp[L][Fstate][i] ;
    printf( "%lld\n" , ans ) ;

    if( ans <= 42 ){
        for( int i = 1 ; i <= totnd ; i ++ ){
            if( dp[L][Fstate][i] )
                dfs( L , Fstate , i , w[i].now - 'a' ) ;
        }
        for( int i = 1 ; i <= ans ; i ++ )
            rank[i] = i ;
        sort( rank + 1 , rank + ans + 1 , cmp ) ;
        for( int i = 1 ; i <= ans ; i ++ )
            printf( "%s\n" , anstr[ rank[i] ] ) ;
    }
}

int main(){
    scanf( "%d%d" , &L , &N ) ;
    for( int i = 1 ; i <= N ; i ++ )
        scanf( "%s" , ss[i] ) ;
    preWork() ;
    solve() ;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值