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状态记录)的主要内容,如果未能解决你的问题,请参考以下文章