1. 程式人生 > >HDU 2825 Wireless Password ( Trie圖 && 狀態壓縮DP )

HDU 2825 Wireless Password ( Trie圖 && 狀態壓縮DP )

inline ble 轉移 top hdu max style 全部 新節點

題意 : 輸入n、m、k意思就是給你 m 個模式串,問你構建長度為 n 至少包含 k 個模式串的方案有多少種

分析 : ( 以下題解大多都是在和 POJ 2778 && POJ 1625 && HDU 2243 進行類比,如果沒做過的話.......可能看不懂 )

這道題如果去對比之前做過的 POJ 2778 And HDU 2243 可以發現現在的難點在於如何找出至少包含 k 個模式串的,這裏我們給每一個單詞編號,對於在DP過程當中選中了這個單詞就標記一下,但是問題是如何判斷是否被重復選過以及如何標記?如果直接開在DP的維度上那是不可能的!(即開一個十維數組來記錄每一個單詞是否被選過),如果能將這十個維度壓縮成一個維度多好啊!沒錯了,使用二進制,二進制的每一位代表每一個單詞,0就是沒被選,1就是被選,最多只需要十位便能記錄這些信息,轉化為十進制之後我們只要給DP再多添一個維度即可。那麽代碼怎麽實現呢?只需要用到 | 操作符即可,這個操作符會把每一位的 1 進行疊加,相當於吧信息進行疊加!不過這裏需要註意,在做POJ 1625 的時候是用一個矩陣來轉移的,而這道題不能,因為我們還附加了一個選了哪幾個單詞這一個維度,如果全部籠統的映射到矩陣上的話是不行的!比如 G[i][j] 代表從 i 到 j 一步走完的可行方案,但是我們不知道這些方案到底走過了那些單詞,所以需要一步步轉移,如果把 POJ 1625 的代碼不用矩陣來儲存信息那要怎麽寫呢?如下( 如果你之前看過我 POJ 1625 的代碼的話 )

        for(int i=0; i<m; i++)
        for(int j=0; j<ac.Size; j++){
//            for(int k=0; k<ac.Size; k++){
//                dp[i+1][k] += dp[i][j] * G[j][k];
//            }
            for(int k=0; k<n; k++){
                int tmp = ac.Node[j].Next[k];
                if(!ac.Node[tmp].flag)
                    dp[i
+1][tmp] += dp[i][j]; } }

所以在網上搜這道題的題解的時候,一開始我有點懵,為什麽有四層for?這和用矩陣有什麽區別?實際上矩陣寫法完全可以寫成如上所示!

因此總結一下就是 利用DP[i][j][k] 表示 DP[第幾步][哪個節點結尾][當前選了哪些單詞] = 方案數

這裏有個大優化,因為我們的DP是一個向前的DP,這樣的DP有一個優勢,就是當 DP值 == 0 的時候將不會對後面有任何影響,直接continue便能減去大量的“枝”!

技術分享
#include<string.h>
#include<stdio.h>
#include
<queue> using namespace std; const int Max_Tot = 111; const int Letter = 26; const int MOD = 20090717; int dp[30][111][(1<<10)+1]; int cnt[1111]; struct Aho{ struct StateTable{ int Next[Letter]; int fail, id; }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].id = 0; Size = 1; } inline void insert(char *s, int id){ 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].id = 0; Node[now].Next[idx] = Size++; } now = Node[now].Next[idx]; } Node[now].id |= (1<<id); } 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].id |= Node[Node[top].fail].id; 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 S[20]; int main(void) { for(int i=0; i<(1<<10); i++){ cnt[i] = 0; for(int j=0; j<10; j++){ if(i & (1<<j)) cnt[i]++; } } int n, m, k; while(~scanf("%d %d %d", &n, &m, &k)){ if(n==0 && m==0 && k==0) break; ac.init(); for(int i=0; i<m; i++){ scanf("%s", S); ac.insert(S, i); } ac.BuildFail(); for(int i=0; i<=n; i++) for(int j=0; j<ac.Size; j++) for(int l=0; l<(1<<m); l++) dp[i][j][l] = 0; dp[0][0][0] = 1; for(int i=0; i<n; i++){ for(int j=0; j<ac.Size; j++){ for(int l=0; l<(1<<m); l++){ if(dp[i][j][l] > 0){ for(int x=0; x<Letter; x++){///不用矩陣了,一步步轉移 int newi = i+1; int newj = ac.Node[j].Next[x]; int newl = (ac.Node[ newj ].id) | l;///加入了哪個新節點就應該去疊加狀態! dp[newi][newj][newl] += dp[i][j][l]; dp[newi][newj][newl] %= MOD; } } } } } int ans = 0; for(int i=0; i<(1<<m); i++){ if(cnt[i]>=k){ for(int j=0; j<ac.Size; j++){ ans = (ans + dp[n][j][i])%MOD; } } } printf("%d\n", ans%MOD); } return 0; }
View Code

HDU 2825 Wireless Password ( Trie圖 && 狀態壓縮DP )