1. 程式人生 > >HDU 2296 Ring ( Trie圖 && DP && DP狀態記錄)

HDU 2296 Ring ( Trie圖 && DP && DP狀態記錄)

最小 truct 答案 需要 維數 每一個 names emp microsoft

題意 : 給出 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狀態記錄)