HDU 2296 Ring ( Trie图 && DP && DP状态记录)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HDU 2296 Ring ( Trie图 && DP && DP状态记录)相关的知识,希望对你有一定的参考价值。

题意 : 给出 m 个单词,每一个单词有一个权重,如果一个字符串包含了这些单词,那么意味着这个字符串拥有了其权重,问你构成长度为 n 且权重最大的字符串是什么 ( 若有权重相同的,则输出最短且字典序最小的 )

 

分析 : 如果你做过 POJ 2778 或者 HDU 2243 以及诸如此类的题目,那么这道题的难点就不在构建 Trie图上了,没有接触过Trie图的建议先了解,下面进入正题。这道题相对于普通的 AC自动机orTrie图 + DP 的题目而言,共同点是都是利用 Trie图进行状态的转移,现在增加了权重以及要求输出具体的字符串答案。我们定义 DP[i][j] 为构建了长度为 i 且最后一个字符为 j 的字符串最大权重,由于每一个状态都对应一个字符串,所以再构建一个三维字符数组 s[i][j][k] 表示当前 i、j 状态下具体的字符串为 s[i][j][0~k-1],那么状态转移方程就是

DP[i+1][ Trie[j][k] ] = max( DP[i+1][ Trie[j][k] ] , DP[i][j] + Trie[j][k].val )

( Trie[j][k] 代表 j 状态可以一步转移到 k状态,如果你做过类似题目,那你不会陌生)

在状态转移的时候需要时时更新 s[i][j][k] 这个三维数组,当取得更优值的时候需要更新,最后只要在DP的过程当中记录最优的权重、状态i、j下标然后DP结束后输出即可。当然有个小优化,这种DP属于向前的DP,如果当前DP值是你设置的初值,那么它是没意义的,可以直接continue,因为它不会对后面的DP值产生影响。

 

#include<string.h>
#include<stdio.h>
#include<queue>
using namespace std;
const int Max_Tot = 1200;
const int Letter = 26;

int dp[55][1200];
char s[55][1200][55];///存储每一个状态所代表的具体字符串

struct Aho{
    struct StateTable{
        int Next[Letter];
        int fail, val;
    }Node[Max_Tot];
    int Size;
    queue<int> que;

    inline void init(){
        while(!que.empty()) que.pop();
        memset(Node[0].Next, 0, sizeof(Node[0].Next));
        Node[0].fail = Node[0].val = 0;
        Size = 1;
    }

    inline void insert(char *s, int val){
        int now = 0;
        for(int i=0; s[i]; i++){
            int idx = s[i] - a;
            if(!Node[now].Next[idx]){
                memset(Node[Size].Next, 0, sizeof(Node[Size].Next));
                Node[Size].fail = Node[Size].val = 0;
                Node[now].Next[idx] = Size++;
            }
            now = Node[now].Next[idx];
        }
        Node[now].val = val;
    }

    inline void BuildFail(){
        Node[0].fail = 0;
        for(int i=0; i<Letter; i++){
            if(Node[0].Next[i]){
                Node[Node[0].Next[i]].fail = 0;
                que.push(Node[0].Next[i]);
            }else Node[0].Next[i] = 0;
        }
        while(!que.empty()){
            int top = que.front(); que.pop();
            Node[top].val += Node[Node[top].fail].val;///这里需要注意!
            for(int i=0; i<Letter; i++){
                int &v = Node[top].Next[i];
                if(v){
                    que.push(v);
                    Node[v].fail = Node[Node[top].fail].Next[i];
                }else v = Node[Node[top].fail].Next[i];
            }
        }
    }
}ac;
char tmp[111][55];
int main(void)
{
    int nCase;
    scanf("%d", &nCase);
    while(nCase--){
        int n, m;
        scanf("%d %d", &n, &m);
        for(int i=0; i<m; i++)
            scanf("%s", tmp[i]);
        int tmpVal;
        ac.init();
        for(int i=0; i<m; i++){
            scanf("%d", &tmpVal);
            ac.insert(tmp[i], tmpVal);
        }
        ac.BuildFail();

        for(int i=0; i<=n; i++){///将所有DP的值赋为 -1
            for(int j=0; j<ac.Size; j++){
                dp[i][j] = -1;
                s[i][j][0] = \0;
            }
        }

        dp[0][0] = 0;///定义初始状态

        char str[60];
        int ii, jj, MaxSum;
        ii = jj = MaxSum = 0;
        for(int i=0; i<n; i++){
            for(int j=0; j<ac.Size; j++){
                if(dp[i][j] >= 0){///如果当前dp值不是初始状态则进入if,否则其dp值毫无意义,直接跳过
                    for(int k=25; k>=0; k--){///一开始我是想谋求字典序最小而从后往前,但是WA一发后我发现我错了,实际上顺序不重要
                        int newi = i+1;
                        int newj = ac.Node[j].Next[k];
                        int sum = dp[i][j] + ac.Node[ newj ].val;
                        if(sum > dp[newi][newj]){
                            dp[newi][newj] = sum;
                            strcpy(s[newi][newj], s[i][j]);
                            int len = strlen(s[i][j]);
                            s[newi][newj][len] = k+a;
                            s[newi][newj][len+1] = \0;
                        }else if(sum == dp[newi][newj]){///谋求字典序最小应该实在dp值相等情况下
                            strcpy(str, s[i][j]);
                            int len = strlen(str);
                            str[len] = a+k;
                            str[len+1] = \0;
                            if(strcmp(str, s[newi][newj]) < 0)
                                strcpy(s[newi][newj], str);
                        }

                        if(dp[newi][newj] >= MaxSum){///更新一下最终的答案
                            if(dp[newi][newj] == MaxSum){
                                int L1 = strlen(s[newi][newj]);
                                int L2 = strlen(s[ii][jj]);
                                if(L1<=L2 && strcmp(s[newi][newj], s[ii][jj])<0)
                                    ii = newi, jj = newj;
                            }else{
                                MaxSum = dp[newi][newj];
                                ii = newi, jj = newj;
                            }
                        }

                    }
                }
            }
        }

        if(MaxSum <= 0) puts("");///如果最后权值依旧是 0 那么输出空串
        else puts(s[ii][jj]);
    }
    return 0;
}

 

以上是关于HDU 2296 Ring ( Trie图 && DP && DP状态记录)的主要内容,如果未能解决你的问题,请参考以下文章

HDU 2296 Ring (AC自动机+DP)

HDU 2296 Ring [AC自动机 DP 打印方案]

HDU2296 Ring(AC自动机+DP)

HDU2296——Ring(AC自动机+DP)

HDU 3499 分层图最短路+Trie树

HDU 2243 ( Trie图 矩阵构造幂和 )