HDU2296 Ring [AC自动机+DP]

  题意是说给你若干个单词,每个单词有一定的分数。如果一个字符串包含了某个单词就会得到该单词的分数,并且可以重复计算。让你输出一个长度不大于N并且分数最大的字符串,分数相等时选最短的,长度相等时选字典序最小的。

  DP方程很容易想,就是最普通的自动机DP。麻烦的地方在于保证字典序,如果从上向下DP,要保存前缀,比较字典序每次要从开头开始比较,可以写出来但是比较麻烦。我是将模式串反过来建图的,这样等于从后向前生成字符串,分数相同时每次选择较小的字符就一定能生成字典序最小的字符串,但要注意当两字符相等时要向前比较直到可以分出字典序为止。

  代码交上去跑了15ms,看statistic已经排在第五了,就是内存稍微多了一点。。。于是。。猥琐的把int改成了short,然后就跑到rank1去了。。嘿嘿。。

#include <stdio.h>
#include <string.h>
#define MAXN 1001
int cas,n,m,sc[101];
char s[101][11];
int next[MAXN][26],fail[MAXN],flag[MAXN],pos;
char nc[MAXN];
int newnode(){
    for(int i=0;i<26;i++)next[pos][i]=0;
    fail[pos]=flag[pos]=0;
    return pos++;
}
void insert(char *s,int sc){
    int len=strlen(s),p=0;
    //将字符串反向插入
    for(int i=len-1;i>=0;i--){
        int k=s[i]-'a';
        int &x=next[p][k];
        p=x?x:x=newnode();
        //nc记录这个位置的字符
        nc[p]=s[i];
    }
    //flag记录走到这一点的分数
    flag[p]=sc;
}
int q[MAXN],front,rear;
void makenext(){
    q[front=rear=0]=0,rear++;
    while(front<rear){
        int u=q[front++];
        for(int i=0;i<26;i++){
            int v=next[u][i];
            if(v==0)next[u][i]=next[fail[u]][i];
            else q[rear++]=v;
            if(u&&v){
                fail[v]=next[fail[u]][i];
                //注意更新节点的价值
                flag[v]+=flag[fail[v]];
            }
        }
    }
}
int d[51][MAXN];
int fath[51][MAXN];
int cmpfath(int now,int u,int v){
    while(now>0){
        if(nc[u]!=nc[v])return nc[u]<nc[v];
        u=fath[now][u],v=fath[now][v];
        now--;
    }
    return 0;
}
void dp(){
    memset(d,-1,sizeof d);
    d[0][0]=0;
    for(int i=0;i<n;i++){
        for(int u=0;u<pos;u++){
            if(d[i][u]==-1)continue;
            for(int k=0;k<26;k++){
                int v=next[u][k];
                //如果从该节点走过来价值比当前价值更高
                if(d[i+1][v]<d[i][u]+flag[v]){
                    fath[i+1][v]=u;
                    d[i+1][v]=d[i][u]+flag[v];
                //价值相等时比较字典序,一直向前比较直到不等
                }else if(d[i+1][v]==d[i][u]+flag[v]){
                    if(cmpfath(i,u,fath[i+1][v]))fath[i+1][v]=u;
                }
            }
        }
    }
    //确定最终走到哪一格,价值优先,其次是长度
    int st=-1,ans=-1;
    for(int i=0;i<=n;i++){
        for(int u=0;u<pos;u++){
            if(d[i][u]<=0)continue;
            if(ans==-1||d[i][u]>d[st][ans]){
                st=i,ans=u;
            }else if(i<=st&&d[i][u]==d[st][ans]){
                if(cmpfath(i,u,ans))st=i,ans=u;
            }
        }
    }
    //因为字符串是反过来的,要从后向前输出答案
    if(ans==-1){
        printf("\n");
    }else{
        for(int now=st,p=ans;now>0;p=fath[now][p],now--)
            printf("%c",nc[p]);
        printf("\n");
    }
}
int main(){
    scanf("%d",&cas);
    while(cas--){
        scanf("%d%d",&n,&m);
        for(int i=0;i<m;i++)scanf("%s",s[i]);
        for(int i=0;i<m;i++)scanf("%d",&sc[i]);
        pos=0;newnode();
        for(int i=0;i<m;i++){
            insert(s[i],sc[i]);
        }
        makenext();
        dp();
    }
    return 0;
}
原文地址:https://www.cnblogs.com/swm8023/p/2627535.html