字符串专题(trie,KMP,AC自动机,manacher)

字符串博大精深,而且算法都比较难以理解(不像图论那么显然)
最近开始钻研大白字符串,顺便看各种blog和刷kuangbin专题
首先是trie树(字典树),这个是最基础的(据说也很重要)
trie树blog地址: http://blog.csdn.net/youngyangyang04/article/details/6840393 

poj1056的代码
#include <map>
#include <set>
#include <stack>
#include <queue>
#include <cmath>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <sstream>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#pragma comment(linker,"/STACK:102400000,102400000")

using namespace std;
#define   MAX       30
#define   MAXN      2000005
#define   lson      l,m,rt<<1
#define   rson      m+1,r,rt<<1|1
#define   lrt       rt<<1
#define   rrt       rt<<1|1
#define   mid       int m=(r+l)>>1
#define   LL        long long
#define   ull       unsigned long long
#define   mem0(x)   memset(x,0,sizeof(x))
#define   mem1(x)   memset(x,-1,sizeof(x))
#define   meminf(x) memset(x,INF,sizeof(x))
#define   lowbit(x) (x&-x)

const LL     mod   = 1000000;
const int    prime = 999983;
const int    INF   = 0x3f3f3f3f;
const int    INFF  = 1e9;
const double pi    = 3.141592653589793;
const double inf   = 1e18;
const double eps   = 1e-10;

/**************读入外挂*********************/
inline int read_int(){
    int ret=0;
    char tmp;
    while(!isdigit(tmp=getchar()));
    do{
        ret=(ret<<3)+(ret<<1)+tmp-'0';
    }
    while(isdigit(tmp=getchar()));
    return ret;
}
/*******************************************/

struct node{
    bool isword;
    int next[15];
    void init(){
        memset(next,0,sizeof(next));
        isword=false;
    }
} tree[100010];
char s[15];
bool ok;
int num;

void insert(char a[]){
    int cou=0;
    int index=0;
    int len=strlen(a);
    for(int i=0; i<len; i++){
        if(tree[index].next[a[i]-'0']==0){
            tree[++num].init();//建立新节点
            tree[index].next[a[i]-'0']=num;//连接
            index=num;
        }
        else{
            cou++;
            index=tree[index].next[a[i]-'0'];
            if(tree[index].isword){
                ok=false;
                return;
            }
        }
    }
    tree[index].isword=true;
    if(cou==len) ok=false;
}

int main(){
    int kase=1;
    ok=true;
    num=0;
    while(cin>>s){
        if(s[0]=='9'){
            if(!ok) printf("Set %d is not immediately decodable\n",kase);
            else printf("Set %d is immediately decodable\n",kase);
            kase++;
            ok=true;
            num=0;
            mem0(tree);
        }
        insert(s);
    }
    return 0;
}


KMP算法

然后开始刷KMP算法,这个算法比较难以理解,百度了个不错的blog,不过优化后有next的KMP还是感觉比较难写(所以我还是用大白上的KMP(MP)模板了)
反正O(m+n)复杂度应该都能过
blog地址: http://blog.csdn.net/joylnwang/article/details/6778316 

hdu 1711:
题意:这么明显,就是从第几个位置开始匹配
题解:KMP模版,用的大白的
int a[MAX];
int b[10005];
int f[10005];
int n,m;

void getFail(){
    f[0]=0;
    f[1]=0;
    for(int i=1;i<m;i++){
        int j=f[i];
        while(j&&b[i]!=b[j]) j=f[j];
        f[i+1]=(b[i]==b[j])?j+1:0;
    }
}

void solve(){
    getFail();
    int j=0;
    int flag=0;
    for(int i=0;i<n;i++){
        while(j&&b[j]!=a[i]) j=f[j];
        if(b[j]==a[i]) j++;
        if(j==m){
            printf("%d\n",i-m+2);//就这个匹配开始的端点要注意下
            flag=1;
            break;
        }
    }
    if(!flag) printf("-1\n");
}
int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        scanf("%d%d",&n,&m);
        for(int i=0;i<n;i++) scanf("%d",&a[i]);
        for(int i=0;i<m;i++) scanf("%d",&b[i]);
        solve();
    }
    return 0;
}

hdu 1686
题意:两个字符串,第一个在第二个中出现多少次
题解:KMP计算匹配次数,理解之后就很容易,每次匹配之后只要计数+1即可,然后匹配时会用失配函数自动从模式串底端往前移
char a[MAX];
char b[10005];
int f[10005];
int n,m;

void getFail(){
    f[0]=0;
    f[1]=0;
    for(int i=1;i<m;i++){
        int j=f[i];
        while(j&&b[i]!=b[j]) j=f[j];
        f[i+1]=(b[i]==b[j])?j+1:0;
    }
}

void solve(){
    getFail();
    int j=0;
    int num=0;
    for(int i=0;i<n;i++){
        while(j&&b[j]!=a[i]) j=f[j];
        if(b[j]==a[i]) j++;
        if(j==m){
            num++;
        }
    }
    printf("%d\n",num);
}

int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        mem0(f);
        scanf("%s%s",b,a);
        n=strlen(a);
        m=strlen(b);
        solve();
    }
    return 0;
}

hdu 2087
题意:一个字符串可以分成多少个第二个字符串
题解:这题和上题差不多,不过就是匹配的都不能重叠,就是每次匹配完成之后都必须把模式串的指针移动到开头
所以只要在上面的代码num++,下面加个j=0即可AC

hdu 3746
题意:给你一个字符串,让你在两端最少添加一些字符,使得整个字符串里有循环
题解:这是用KMP求最小循环节,比如aaa最小循环节是a,所以不需要添加,
abca,最小循环节是abc所以添加2
abcde最小循环节是abcde所以添加5
这里就要用到失配函数的作用了
这边有一些结论 http://www.cnblogs.com/wuyiqi/archive/2012/01/06/2314078.html
看完这个证明应该就理解了
len-f[len]就是最小循环节
char a[MAX];
char b[100005];
int f[100005];
int n,m;

void getFail(){
    f[0]=0;
    f[1]=0;
    for(int i=1;i<m;i++){
        int j=f[i];
        while(j&&b[i]!=b[j]) j=f[j];
        f[i+1]=(b[i]==b[j])?j+1:0;
    }
}

int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        scanf("%s",b);
        m=strlen(b);
        getFail();
        if(f[m]==0) printf("%d\n",m);
        else{
            int t=m-f[m];
            if(m%t==0) printf("0\n");
            else printf("%d\n",t-m%t);
        }
    }
    return 0;
}

hdu 1358
题意:对应每个i,从头开始长度为i的字符串能被最小循环节长度整除,把长度和需要几个最小循环节输出
题解:KMP嘛怒套一波(失配函数极为重要必须好好理解)

char a[MAX];
char b[MAX];
int f[MAX];
int n,m;

void getFail(){
    f[0]=0;
    f[1]=0;
    for(int i=1;i<n;i++){
        int j=f[i];
        while(j&&b[i]!=b[j]) j=f[j];
        f[i+1]=(b[i]==b[j])?j+1:0;
    }
}

int main(){
    int kase=0;
    while(scanf("%d",&n)&&n){
        kase++;
        printf("Test case #%d\n",kase);
        scanf("%s",b);
        getFail();
        for(int i=2;i<=n;i++){
            if(f[i]==0) continue;
            int t=i-f[i];
            if(i%t==0) printf("%d %d\n",i,i/t);
        }
        printf("\n");
    }
    return 0;
}

hust 1010
直接求的最小循环节的长度m-f[m]


poj 2406
题意:求最小循环节周期,如果没有最小循环节或者不能被最小循环节整除,那么就输出1

char a[MAX];
char b[MAX];
int f[MAX];
int n,m;

void getFail(){
    f[0]=0;
    f[1]=0;
    for(int i=1;i<m;i++){
        int j=f[i];
        while(j&&b[i]!=b[j]) j=f[j];
        f[i+1]=(b[i]==b[j])?j+1:0;
    }
}


int main(){
    while(scanf("%s",&b)){
        if(b[0]=='.') break;
        //getchar();
        m=strlen(b);
        getFail();
        //printf("%d ",f[m]);
        if(f[m]>0&&m%(m-f[m])==0) printf("%d\n",m/(m-f[m]));
        else printf("1\n");
    }
    return 0;
}


poj 2752
题意:给你一个字符串,让你在这个字符串中寻找前缀等于后缀的所有字符串的长度
题解:getFail一发,然后f[m]的值就是最长的前缀等于后缀的字符串的长度,然后这样想,整个字符串的前缀等于后缀,就是f[m]中的第一个字符串的前缀和第二个字符串的后缀,所以只需要求第一个字符串中前后缀的长度就是f[f[m]]
char a[MAX];
char b[MAX];
int f[MAX];
int ans[MAX];
int n,m;

void getFail(){
    f[0]=0;
    f[1]=0;
    for(int i=1;i<m;i++){
        int j=f[i];
        while(j&&b[i]!=b[j]) j=f[j];
        f[i+1]=(b[i]==b[j])?j+1:0;
    }
}


int main(){
    while(~scanf("%s",b)){
        m=strlen(b);
        getFail();
        int tot=0;
        ans[tot++]=m;
        int k=m;
        while(f[k]){
            ans[tot++]=f[k];
            //printf("%d %d\n",k,f[k]);
            k=f[k];
        }
        sort(ans,ans+tot);
        for(int i=0;i<tot;i++){
            if(i!=tot-1) printf("%d ",ans[i]);
            else printf("%d\n",ans[i]);
        }
    }
    return 0;
}

HDU 2594
题意:给你两个字符串,然后找第一个串的最长前缀是第二个串的后缀
题解:自然是KMP咯,但是我一开始太弱了,我直接枚举第一个串的前缀长度,然后TLE
后来发现,如果把两个串加到一起,然后求f[m],然后就是最长前缀,为啥呢,因为f[m]是第m位失配后转移过去的地方,然后0-f[m]这些字符都和最后的那些匹配,才能转移的,我太二了,这不就是妥妥的失配函数搞一下么,好久没做KMP了,有些遗忘失配函数的妙用
AC代码 : http://paste.ubuntu.net/12712552/

当然这题妥妥的还可以用哈希来一炮,我哈希一直不是太会写,这次来了一发复习了一下
AC代码 : http://paste.ubuntu.net/12712556/


HDU 3336
题意:给你个字符串,然后问你里面有多少个前缀,就是这个字符串的每个前缀,在字符串里出现的次数的总和
题解:KMP+dp
又要巧妙利用失配函数了,就是dp[i]是以i结尾的是前缀的字符串有多少个,然后用失配函数转移一下,就是dp[i]=dp[f[i]]+1,然后每个dp[i]加起来就行了
AC代码 : http://paste.ubuntu.net/12714547/

/********************************************************************************************************************************************************************************************/
AC自动机大法好
理解KMP之后还是很好理解AC自动机的 http://blog.csdn.net/niushuai666/article/details/7002823
就是trie树上的KMP

问年神要了一波模版,感觉自己萌萌哒

hdu 2222
题解:第一个AC自动机模版题

#include <map>
#include <set>
#include <stack>
#include <queue>
#include <cmath>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <sstream>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#pragma comment(linker,"/STACK:102400000,102400000")

using namespace std;
#define   MAX         1000005
#define   MAXN        2000005
#define   maxnode     500010
#define   sigma_size  30
#define   lson        l,m,rt<<1
#define   rson        m+1,r,rt<<1|1
#define   lrt         rt<<1
#define   rrt         rt<<1|1
#define   mid         int m=(r+l)>>1
#define   LL          long long
#define   ull         unsigned long long
#define   mem0(x)     memset(x,0,sizeof(x))
#define   mem1(x)     memset(x,-1,sizeof(x))
#define   meminf(x)   memset(x,INF,sizeof(x))
#define   lowbit(x)   (x&-x)

const LL     mod   = 1000000;
const int    prime = 999983;
const int    INF   = 0x3f3f3f3f;
const int    INFF  = 1e9;
const double pi    = 3.141592653589793;
const double inf   = 1e18;
const double eps   = 1e-10;

/**************读入外挂*********************/
inline int read_int(){
    int ret=0;
    char tmp;
    while(!isdigit(tmp=getchar()));
    do{
        ret=(ret<<3)+(ret<<1)+tmp-'0';
    }
    while(isdigit(tmp=getchar()));
    return ret;
}
/*******************************************/

struct AC{
    int ch[maxnode][sigma_size];
    int val[maxnode], last[maxnode], f[maxnode];
    //int cnt[maxnode];     //cnt是该模块出现过多少次。。该题目取最大的输出
    int cnt;
    int sz;
    void init(){
        mem0(ch[0]); //mem0(cnt);
        last[0]=val[0]=0;
        sz = 1;
        cnt=0;
    }
    int idx(char c){return c-'a';}
    void insert(const char *s,int v){
        int n = strlen(s), u =0;
        for(int i=0;i<n;i++){
            int c = idx(s[i]);
            if(!ch[u][c]){
                mem0(ch[sz]);
                val[sz]=0;
                ch[u][c]=sz++;
            }
            u = ch[u][c];
        }
        val[u]+=v;//考虑单词重复出现
    }
    void bfs(){
        queue<int>q;
        f[0]=0;
        for(int c = 0; c<sigma_size;c++){
            int u = ch[0][c];
            if(u){q.push(u); f[u]=last[u]=0;}
        }
        while(!q.empty()){
            int r =q.front(); q.pop();
            for(int c = 0;c<sigma_size;c++){
                int u = ch[r][c];
                if(!u){ ch[r][c] = ch[f[r]][c]; continue;}//若不要前面那句,则要加下面那句
                q.push(u);
                int v = f[r];
                while(v && !ch[v][c]) v = f[v];
                f[u] = ch[v][c];
                last[u] = val[f[u]] ? f[u] : last[f[u]];
            }
        }
    }
    void find(char *T){
        int n = strlen(T);
        int u = 0;
        for(int i=0;i<n;i++){
            int c = idx(T[i]);
            //while(u && !ch[u][c]) u = f[u];
            u = ch[u][c];
            if(val[u]) print(u);
            else if(last[u]) print(last[u]);
        }
    }
    void print(int j){
        if(j){
            cnt+=val[j];
            val[j]=0;
            print(last[j]);
        }
    }
}ac;
char a[MAX];

int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        int n;
        scanf("%d",&n);
        ac.init();
        char s[55];
        for(int i=0;i<n;i++){
            scanf("%s",s);
            ac.insert(s,1);
        }
        ac.bfs();
        scanf("%s",a);
        ac.find(a);
        printf("%d\n",ac.cnt);
    }
    return 0;
}

hdu 2896
题解:就是问你出现过哪几个字符串

这里改一下就行了
void print(int j){
        if(j){
            vis[val[j]]=1;
            flag=1;
            print(last[j]);
        }
    }
 
hdu 3065

题解:计数

void print(int j){
        if(j){
            cnt[val[j]]++;
            print(last[j]);
        }
    }

poj 2778
题意:给你一些字符串,然后给你个长度n,问你长度n的字符串里,不出现前面那些字符串的情况的总个数
题解:AC自动机+矩阵快速幂

这题一开始看见无从下手,后来看了题解之后原来是和矩阵扯上了关系
矩阵可以当成图,上面的元素a[i][j]就是从点i到j的路的条数
矩阵: http://www.matrix67.com/blog/archives/276
此题详细题解: http://blog.csdn.net/morgan_xww/article/details/7834801
此博客的图比较详细

就是先把这些字符串用AC自动机处理,有0-ac.sz-1 一共ac.sz个点,然后每个点之间的路,就是给的字符串的连接,还有就是失配函数也是两点之间的路径,还有如果当前字符是A,然后到达节点之后下面没有A的路,那么就是等于在这个点有自环(那个博客里的图十分清楚)

但是在AC自动机处理时,遇到字符串结尾,要用val标记,还有last标记,在构造矩阵时,如果val[v]或者last[v]不为0的话,意思就是这边出现了trie树中的字符串,那么就是不可取的,所以构造矩阵时,只有走到不是危险节点的这条边才能添加进矩阵

然后就是n次矩阵快速幂(我的模版有毒,不造为何一阶矩阵算出来一直是0,改了半天也没好,所以从此我就套用年神的矩阵模版了)

#include <map>
#include <set>
#include <stack>
#include <queue>
#include <cmath>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <sstream>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#pragma comment(linker,"/STACK:102400000,102400000")

using namespace std;
#define   MAX         100005
#define   MAXN        2000005
#define   maxnode     110
#define   sigma_size  4
#define   lson        l,m,rt<<1
#define   rson        m+1,r,rt<<1|1
#define   lrt         rt<<1
#define   rrt         rt<<1|1
#define   mid         int m=(r+l)>>1
#define   LL          long long
#define   ull         unsigned long long
#define   mem0(x)     memset(x,0,sizeof(x))
#define   mem1(x)     memset(x,-1,sizeof(x))
#define   meminf(x)   memset(x,INF,sizeof(x))
#define   lowbit(x)   (x&-x)


const int    prime = 999983;
const int    INF   = 0x3f3f3f3f;
const int    INFF  = 1e9;
const double pi    = 3.141592653589793;
const double inf   = 1e18;
const double eps   = 1e-10;
const int    mod   = 100000;

/**************¶ÁÈëÍâ¹Ò*********************/
inline int read_int(){
    int ret=0;
    char tmp;
    while(!isdigit(tmp=getchar()));
    do{
        ret=(ret<<3)+(ret<<1)+tmp-'0';
    }
    while(isdigit(tmp=getchar()));
    return ret;
}
/*******************************************/

struct AC{
    int ch[maxnode][sigma_size];
    int val[maxnode], last[maxnode], f[maxnode];
    int sz;
    void init(){
        mem0(ch[0]);
        last[0]=val[0]=0;
        sz = 1;
    }
    int idx(char c){
        switch(c){
            case 'A': return 0;
            case 'T': return 1;
            case 'C': return 2;
            case 'G': return 3;
            default: return -1;
        }
    }
    void insert(const char *s,int v){
        int n = strlen(s), u =0;
        for(int i=0;i<n;i++){
            int c = idx(s[i]);
            if(c==-1) return ;
            if(!ch[u][c]){
                mem0(ch[sz]);
                val[sz]=0;
                ch[u][c]=sz++;
            }
            u = ch[u][c];
        }
        val[u]=v;
    }
    void bfs(){
        queue<int>q;
        f[0]=0;
        for(int c = 0; c<sigma_size;c++){
            int u = ch[0][c];
            if(u){q.push(u); f[u]=last[u]=0;}
        }
        while(!q.empty()){
            int r =q.front(); q.pop();
            for(int c = 0;c<sigma_size;c++){
                int u = ch[r][c];
                if(!u){ ch[r][c] = ch[f[r]][c]; continue;}//若不要前面那句,则要加下面那句
                q.push(u);
                int v = f[r];
                while(v && !ch[v][c]) v = f[v];
                f[u] = ch[v][c];
                last[u] = val[f[u]] ? f[u] : last[f[u]];
            }
        }
    }
}ac;

struct Matrix{
    int n;
    LL maze[maxnode][maxnode];

    void init(int n){
        this->n = n;
        mem0(maze);
    }
    Matrix operator * (Matrix &rhs){
        Matrix m;
        m.init(n);
        for(int i=0;i<n;i++)
            for(int j=0;j<n;j++)
                for(int k=0;k<n;k++)
                    m.maze[i][j] = (m.maze[i][j] + maze[i][k] * rhs.maze[k][j])%mod;
        return m;
    }
};

int qpow(Matrix a,int n){
    Matrix ans;
    ans.init(a.n);
    for(int i=0;i<ans.n;i++) ans.maze[i][i] = 1;
    while(n){
        if(n&1) ans = ans * a;
        a = a*a;
        n >>= 1;
    }
    int sum = 0;
    for(int i=0;i<ans.n;i++) sum = (sum + ans.maze[0][i]) % mod;
    return sum;
}

char s[20];

int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    ac.init();
    for(int i=0;i<n;i++){
        scanf("%s",s);
        ac.insert(s,1);
    }
    ac.bfs();
    Matrix A;
    A.init(ac.sz);
    for(int i=0;i<ac.sz;i++){
        for(int j=0;j<sigma_size;j++){
            int v=ac.ch[i][j];
            if(ac.val[v]) continue;
            else if(ac.last[v]) continue;
            A.maze[i][v]++;
        }
    }
    printf("%d\n",qpow(A,m));
    return 0;
}

/**************************************************************************************************************************************************************************************************/
字符串 dp

poj3691
题意:给你n个字符串,这是危险 字符串,然后给你一个字符串,让你最少修改多少次,使里面没有危险字符串出现,如果不能做到就-1
题解:危险字符串先构造AC自动机,建立危险节点,然后在给的字符串上做dp
二维dp[i][j],表示字符串的第i位匹配到AC自动机上的j状态时最少需要修改的次数
以前一直搞不清如何转移,发现题解里也都写的不清楚,在这里提一下,这个i匹配到j状态时,从trie树上root到j的字符都与字符串的后缀都匹配了,才能有dp[i][j]的状态,如果i-1和j状态前一个字符不匹配,那么就要+1,把i-1修改

说到这里应该很清楚了,字符串挺难,主要是理解和思考,我还刚刚起步,多看书多敲题

转移方程 dp[i][ch[j][k]] = min (dp[i][ch[j][k]] , dp[i-1][j] + s[i-1]!=k )
ch[j][k]是j节点下的字符k(A,T,C,G)所转移到的节点(通过失配函数)
如果s[i-1]不等于k,那么就要修改i-1

还有如果转移到的点是危险节点,那么就不做转移

最后只要遍历dp[len][i]找到最小值即可,这是字符串的len长度匹配到一个不是危险节点时候需要修改的次数

#include <map>
#include <set>
#include <stack>
#include <queue>
#include <cmath>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <sstream>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#pragma comment(linker,"/STACK:102400000,102400000")

using namespace std;
#define   MAX         5005
#define   MAXN        1000005
#define   maxnode     1005
#define   sigma_size  4
#define   lson        l,m,rt<<1
#define   rson        m+1,r,rt<<1|1
#define   lrt         rt<<1
#define   rrt         rt<<1|1
#define   mid         int m=(r+l)>>1
#define   LL          long long
#define   ull         unsigned long long
#define   mem(x,v)    memset(x,v,sizeof(x))
#define   lowbit(x)   (x&-x)


const int    prime = 999983;
const int    INF   = 0x3f3f3f3f;
const int    INFF  = 1e9;
const double pi    = 3.141592653589793;
const double inf   = 1e18;
const double eps   = 1e-10;
const LL     mod   = (1<<64);

/**************¶ÁÈëÍâ¹Ò*********************/
inline int read_int(){
    int ret=0;
    char tmp;
    while(!isdigit(tmp=getchar()));
    do{
        ret=(ret<<3)+(ret<<1)+tmp-'0';
    }
    while(isdigit(tmp=getchar()));
    return ret;
}
/*******************************************/

struct AC{
    int ch[maxnode][sigma_size];
    int val[maxnode], last[maxnode], f[maxnode];
    //int cnt[maxnode];     //cnt是该模块出现过多少次。。该题目取最大的输出
    int cnt;
    int sz;
    void init(){
        mem(ch[0],0); //mem0(cnt);
        last[0]=val[0]=0;
        sz = 1;
        cnt=0;
    }
    int idx(char c){
        switch(c){
            case 'A':return 0;
            case 'T':return 1;
            case 'C':return 2;
            case 'G':return 3;
        }
    }
    void insert(const char *s,int v){
        int n = strlen(s), u =0;
        for(int i=0;i<n;i++){
            int c = idx(s[i]);
            if(!ch[u][c]){
                mem(ch[sz],0);
                val[sz]=0;
                ch[u][c]=sz++;
            }
            u = ch[u][c];
        }
        val[u]=v;//考虑单词重复出现
    }
    void bfs(){
        queue<int>q;
        f[0]=0;
        for(int c = 0; c<sigma_size;c++){
            int u = ch[0][c];
            if(u){q.push(u); f[u]=last[u]=0;}
        }
        while(!q.empty()){
            int r =q.front(); q.pop();
            for(int c = 0;c<sigma_size;c++){
                int u = ch[r][c];
                if(!u){ ch[r][c] = ch[f[r]][c]; continue;}//若不要前面那句,则要加下面那句
                q.push(u);
                int v = f[r];
                while(v && !ch[v][c]) v = f[v];
                f[u] = ch[v][c];
                last[u] = val[f[u]] ? f[u] : last[f[u]];
            }
        }
    }
    void find(char *T){
        int n = strlen(T);
        int u = 0;
        for(int i=0;i<n;i++){
            int c = idx(T[i]);
            //while(u && !ch[u][c]) u = f[u];
            u = ch[u][c];
            if(val[u]) print(u);
            else if(last[u]) print(last[u]);
        }
    }
    void print(int j){
        if(j){
            print(last[j]);
        }
    }
}ac;
char s[MAX];
int dp[1005][1005];

int main(){
    int n,kase=0;
    while(scanf("%d",&n)&&n){
        ac.init();
        for(int i=0;i<n;i++){
            scanf("%s",s);
            ac.insert(s,1);
        }
        ac.bfs();
        scanf("%s",s);
        int len=strlen(s);
        mem(dp,INF);
        dp[0][0]=0;
        for(int i=1;i<=len;i++){
            for(int j=0;j<ac.sz;j++){
                if(dp[i-1][j]==INF) continue;
                for(int k=0;k<4;k++){
                    int p=ac.ch[j][k];     //j状态下k转移去的节点
                    if(ac.val[p]==0&&ac.last[p]==0){    
                        dp[i][p]=min(dp[i][p],dp[i-1][j]+(ac.idx(s[i-1])!=k));
                    }
                }
            }
        }
        int mini=INF;
        for(int i=0;i<ac.sz;i++){
            mini=min(mini,dp[len][i]);
        }
        kase++;
        printf("Case %d: ",kase);
        if(mini==INF) printf("-1\n");
        else printf("%d\n",mini);
    }
    return 0;
}

hdu 2243

题意:给你n个字符串,问你长度小于等于m的字符串中必须出现这n个其中一个子串的字符串有多少情况

题解:前面做过一定不能出现,现在是反过来考虑,所以先考虑所有字符串,就是从26^1+26^2+......+26^m,这个用快速幂+递归求等比数列就好了

然后是计算长度小于等于m的字符串中不能出现上述字符串的个数,两个相减就是结果

由于是模1<<64,直接用unsigned long long即可,会自动模

难点就在于这个n个字符串组成的矩阵

首先是构造AC自动机,然后如果val[j],last[j]不为0的话就continue,为0的地方就在矩阵上++(可以到达)

然后就是求A^1+A^2+……+A^m这个矩阵的第一行的和

如果用快速幂+递归很有可能爆掉

矩阵是一种十分奇妙的东西,假如矩阵A是n行n列,年神教我开成n+1行n+1列,最后一行都是0,最后一列都是1(A[n+1][n+1]=1)

这样的话两个矩阵相乘,第一行的前n个,还是和n*n的两个A相乘一样,第一行的最后一个值,就是n+1个1和另外个A的第一行相乘,值是A的第一行的值+1

以此类推,第二次得到的最后一个值就是A^2+A的第一行的值+1,最后得到的就是A^m-1+……+1,然后只要把最后得到的矩阵的第一行相加即可


如此巧妙的技巧我必须以后多加练习,矩阵的优化是十分的奇妙

#include <map>
#include <set>
#include <stack>
#include <queue>
#include <cmath>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <sstream>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#pragma comment(linker,"/STACK:102400000,102400000")

using namespace std;
#define   MAX         100005
#define   MAXN        2000005
#define   maxnode     110
#define   sigma_size  26
#define   lson        l,m,rt<<1
#define   rson        m+1,r,rt<<1|1
#define   lrt         rt<<1
#define   rrt         rt<<1|1
#define   mid         int m=(r+l)>>1
#define   LL          long long
#define   ull         unsigned long long
#define   mem0(x)     memset(x,0,sizeof(x))
#define   mem1(x)     memset(x,-1,sizeof(x))
#define   meminf(x)   memset(x,INF,sizeof(x))
#define   lowbit(x)   (x&-x)


const int    prime = 999983;
const int    INF   = 0x3f3f3f3f;
const int    INFF  = 1e9;
const double pi    = 3.141592653589793;
const double inf   = 1e18;
const double eps   = 1e-10;
const LL     mod   = (1<<64);

/**************¶áèëía1ò*********************/
inline int read_int(){
    int ret=0;
    char tmp;
    while(!isdigit(tmp=getchar()));
    do{
        ret=(ret<<3)+(ret<<1)+tmp-'0';
    }
    while(isdigit(tmp=getchar()));
    return ret;
}
/*******************************************/

struct AC{
    int ch[maxnode][sigma_size];
    int val[maxnode], last[maxnode], f[maxnode];
    int sz;
    void init(){
        mem0(ch[0]);
        last[0]=val[0]=0;
        sz = 1;
    }
    int idx(char c){return c-'a';}
    void insert(const char *s,int v){
        int n = strlen(s), u =0;
        for(int i=0;i<n;i++){
            int c = idx(s[i]);
            if(c==-1) return ;
            if(!ch[u][c]){
                mem0(ch[sz]);
                val[sz]=0;
                ch[u][c]=sz++;
            }
            u = ch[u][c];
        }
        val[u]=v;
    }
    void bfs(){
        queue<int>q;
        f[0]=0;
        for(int c = 0; c<sigma_size;c++){
            int u = ch[0][c];
            if(u){q.push(u); f[u]=last[u]=0;}
        }
        while(!q.empty()){
            int r =q.front(); q.pop();
            for(int c = 0;c<sigma_size;c++){
                int u = ch[r][c];
                if(!u){ ch[r][c] = ch[f[r]][c]; continue;}//若不要前面那句,则要加下面那句
                q.push(u);
                int v = f[r];
                while(v && !ch[v][c]) v = f[v];
                f[u] = ch[v][c];
                last[u] = val[f[u]] ? f[u] : last[f[u]];
            }
        }
    }
}ac;

struct Matrix{
    int n;
    ull maze[maxnode][maxnode];

    void init(int n){
        this->n = n;
        mem0(maze);
    }
    Matrix operator * (Matrix &rhs){
        Matrix m;
        m.init(n);
        for(int i=0;i<n;i++)
            for(int j=0;j<n;j++)
                for(int k=0;k<n;k++)
                    m.maze[i][j] = m.maze[i][j] + maze[i][k] * rhs.maze[k][j];
        return m;
    }
    Matrix operator + (Matrix &rhs){
        Matrix m;
        m.init(n);
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                m.maze[i][j] = maze[i][j] + rhs.maze[i][j];
            }
        }
        return m;
    }
};

ull qpow(Matrix a,int n){
    Matrix ans;
    ans.init(a.n);
    for(int i=0;i<ans.n;i++) ans.maze[i][i] = 1;
    while(n){
        if(n&1) ans = ans * a;
        a = a * a;
        n >>= 1;
    }
    ull tmp=0;
    for(int i=0;i<ans.n;i++) tmp+=ans.maze[0][i];
    return tmp;
}

ull ppow(ull a,int m){
    ull ans=1;
    ull b=a;
    while(m){
        if(m&1) ans*=b;
        b*=b;
        m>>=1;
    }
    return ans;
}

char s[20];

int main(){
    int n,m;
    while(~scanf("%d%d",&n,&m)){
        ac.init();
        for(int i=0;i<n;i++){
            scanf("%s",s);
            ac.insert(s,1);
        }
        ac.bfs();
        Matrix A;
        A.init(ac.sz+1);
        for(int i=0;i<ac.sz;i++){
            for(int j=0;j<sigma_size;j++){
                int v=ac.ch[i][j];
                if(ac.val[v]) continue;
                else if(ac.last[v]) continue;
                A.maze[i][v]++;
            }
        }
        for(int i=0;i<=ac.sz;i++) A.maze[i][ac.sz]=1;
        Matrix B;
        B.init(2);
        B.maze[0][0]=26;B.maze[0][1]=1;
        B.maze[1][0]=0; B.maze[1][1]=1;
        ull ans=qpow(B,m);
        ull tmp=qpow(A,m);
        cout<<ans-tmp<<endl;
    }
    return 0;
}


/***********************************************************************************************************************************************************************************************/
manacher
manacher算法是计算最长回文子串的好方法,只要O(n)的复杂度即可搞定
算法blog ; http://blog.csdn.net/ggggiqnypgjg/article/details/6645824/

HDU 3068
模版题,贴个模版
AC代码: http://paste.ubuntu.net/12636200/

hdu 3294
这题先是要字符变换,然后就是求最长回文的区间位置
其实就是求出最长回文之后,计算左右区间,然后输出就好了,开头用了strncpy就是WA,不明白啊
AC代码 : http://paste.ubuntu.net/12636209/





你可能感兴趣的:(ACM)