日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

动态规划之状态机模型

發布時間:2025/3/19 编程问答 14 豆豆
生活随笔 收集整理的這篇文章主要介紹了 动态规划之状态机模型 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

狀態機模型

  • 狀態機
  • 狀態機與動態規劃之間的聯系
    • 狀態機的本質之動態規劃描述
  • 股票買賣系列
    • 股票買賣
    • 股票買賣Ⅱ
      • 一、動態規劃解法
      • 二、貪心解法
    • 股票買賣Ⅲ
      • 前后綴分解
    • 股票買賣Ⅳ
    • 股票買賣Ⅴ
  • KMP + 狀態機
    • 設計密碼
  • AC自動機 + 狀態機
    • 修復DNA

狀態機

下面來給出狀態機的四大概念。

第一個是 State ,狀態。一個狀態機至少要包含兩個狀態。例如上面自動門的例子,有 open 和 closed 兩個狀態。
第二個是 Event ,事件。事件就是執行某個操作的觸發條件或者口令。對于自動門,“按下開門按鈕”就是一個事件。
第三個是 Action ,動作。事件發生以后要執行動作。例如事件是“按開門按鈕”,動作是“開門”。編程的時候,一個 Action一般就對應一個函數。
第四個是 Transition ,變換。也就是從一個狀態變化為另一個狀態。例如“開門過程”就是一個變換。
2、什么是狀態機?
有限狀態機(Finite-state machine,FSM),又稱有限狀態自動機,簡稱狀態機,是表示有限個狀態以及在這些狀態之間的轉移和動作等行為的數學模型。
FSM是一種算法思想,簡單而言,有限狀態機由一組狀態、一個初始狀態、輸入和根據輸入及現有狀態轉換為下一個狀態的轉換函數組成。
其作用主要是描述對象在它的生命周期內所經歷的狀態序列,以及如何響應來自外界的各種事件。
做需求時,需要了解以下六種元素:起始、終止、現態、次態(目標狀態)、動作、條件,我們就可以完成一個狀態機圖了:

參考博文
狀態機

狀態機與動態規劃之間的聯系

我們只是利用狀態機來更好的描述動態規劃的這個過程。也就是我用狀態機來表示動態規劃。
我們引入大盜阿福這個題目來說明。我們可以定義f[i]f[i]f[i]表示前i加店鋪的最大價值。那么我們有

在這里我們的每一個f[i]f[i]f[i]都只有一個狀態,表示的是考慮到第i個店鋪了的最大價值。而由轉態機的定義我們知道至少要有兩個狀態;那么如果我們這樣表示

這里的第二維的含義和一般的不一樣。好比背包問題中f[i][j]f[i][j]f[i][j]表示前i個物品體積為j的最大價值。這里i和j共同表示了一個轉態。而本題中是對于i而言,它有兩個狀態。而這兩個狀態之間又有聯系:

原本我們動態規劃的每一個狀態是一個節點,現在,一個節點分為了兩個轉態來描述。

我們在進行狀態轉移的時候,是滿足某些條件之后自動轉移。

狀態機的本質之動態規劃描述

狀態機本質上是一個有向圖(可以有環),每一個點都是一個狀態,每一條邊都是某種情況。如果滿足某種情況就會跳轉到節點上

股票買賣系列

股票買賣

題目轉送門

題目大意:第一個問題是股票交易最多只能進行一次。

解題思路:
這里我們可以利用貪心來求解,當遍歷到第i個股票的時候,我們利用一個數存前i-1個值的最小值,之后有 res = max(res , w[i] - minv) ;之后更新最小值。

代碼:

#include<iostream> #include<algorithm> #include<cstring> using namespace std; const int N = 1E5 + 10 , INF = 0x3f3f3f3f; int n ,w[N];int main(){cin>>n;for(int i = 1; i <= n ; ++i)cin>>w[i];int res = 0;int tp = w[1];for(int i = 2; i <= n ; ++i){res = max(res , w[i] - tp);tp = min(tp , w[i]);}cout<<res<<endl;return 0; }

這題的動態規劃寫法可以直接套用股票買賣Ⅳ就可以了,將k改為1即可。

股票買賣Ⅱ

題目轉送門

題目大意:這一題交易的次數不限

解題思路:

一、動態規劃解法


一共只2有種狀態:

  • 當前處于未持股狀態0:
    對應可以進行的轉換:
    0->0 (不買入,繼續觀望,那么就什么都不發生)
    0->1 (買入股票,那么收益就要減去當前市場的股票價格)
  • 當前處于持股狀態1:
    對應可以進行的轉換:
    1->1 (不賣出,繼續觀望,那么就什么都不發生)
    1->0 (賣出股票,那么收益就要加上當前市場的股票價格)
  • 代碼:

    #include<iostream> #include<algorithm> #include<cstring> using namespace std; const int N = 1e5 + 10; int n ,w[N]; int f[N][2]; int main(){cin>>n;for(int i = 1; i <= n ; ++i)cin>>w[i];memset(f , -0x3f , sizeof f);f[0][0] = 0;for(int i = 1 ; i <= n ; ++i){f[i][0] = max(f[i-1][0] ,f[i-1][1] + w[i]);f[i][1] = max(f[i-1][1] ,f[i-1][0] - w[i]);}cout<<max(f[n][1] ,f[n][0])<<endl;return 0; }

    二、貪心解法

    縱然,我們可以用DP搜索出所有的方案數,但是通過觀察,我們可以發現本題最優解方案存在一定的性質。

    我先貼出測試樣例的折線圖形式(綠色標出下降,紅色標出上升):

    考慮一種方案,在每次上升的前一天購入股票,并在上升后的當天賣出的方案

    if (w[i] > w[i - 1])
    res += w[i] - w[i - 1];
    接下來證明該貪心思路得出的方案即是最優解。

    (1)證明貪心解 ≥≥ 最優解:
    由于貪心解都是取區間長度為 11 的解,因此假設存在于最優解中的某個區間 [i,j][i,j] 的長度 >1>1
    那么會出現一下三種情況:

    對應三種情形:最優解選取的區間最終點位于上方、下方、相等。

    對于情形一:顯然 最優解 << 貪心解
    對于情形二:顯然 最優解 << 貪心解
    對于情形三:毫無疑問,這就是存在于貪心解中的情形,因此 貪心解 == 最優解

    得證

    (2)證明貪心解 ≤≤ 最優解:
    這部分無需證明,因為貪心解即是合法解,所以他的方案必定大于等于最優解

    出處

    代碼:

    #include<iostream> #include<algorithm> #include<cstring> using namespace std; const int N = 1e5 + 10; int n ,w[N];int main(){cin>>n;for(int i = 1; i <= n ; ++i)cin>>w[i];int res = 0;for(int i = 1; i < n ; ++i)res += max(0 , w[i+1] - w[i]);cout<<res<<endl;return 0; }

    股票買賣Ⅲ

    題目轉送門

    解題思路:
    這一題如果用動態規劃解的話,只需要將股票買賣Ⅳ中的K該位3即可。

    前后綴分解

    代碼:

    #include<iostream> using namespace std; const int N = 1e5+10; int dp1[N],dp2[N]; int a[N]; int main() {int n;cin>>n;for(int i = 1; i <= n; i ++) cin>>a[i];int t = 1e5;for(int i = 1; i <= n; i ++){t = min(t,a[i]);//記錄前面最小的數dp1[i] = max(dp1[i-1],a[i] - t);}t = -1;for(int i = n; i >= 1; i --){t = max(t,a[i]);//因為是從后面開始記錄,那么取后面最大的賺取的利潤才最大。dp2[i] = max(dp2[i+1],t - a[i]);}int res = -1;for(int i = 1; i <= n; i ++)res = max(res,dp1[i] + dp2[i+1]);cout<<res;return 0; }

    股票買賣Ⅳ

    題目轉送門

    解題思路:

    對于什么時候是j什么時候是j-1的問題,我們需要從狀態表示的含義進行出發;我們以這個為例來分析,
    對于f[i,j,0]f[i,j,0]f[i,j,0]我們從有貨變成無貨的話,那么意義應該是前i-1個物品,我們已經交易完j-1次交易,且目前有第j張股票,我們將其賣去,得的價值是今天股票的價值w[i[w[i[w[i[因為今天股票的價格是固定的,我們只需將前邊的收益最大化。而這正好是f[i,j,1]f[i,j,1]f[i,j,1]的含義。

    同時對于狀態機,狀態機的入口全部初始化為0,非入口全初始化為無窮。

    代碼:

    #include<iostream> #include<algorithm> #include<cstring> using namespace std; const int N = 1e5 + 10; int n , w[N] , K; int f[N][110][2];int main(){cin>>n>>K;for(int i = 1 ; i <= n ; ++i)cin>>w[i];memset(f , -0x3f , sizeof f );for(int i = 0 ; i <= n ; ++i)f[i][0][0] = 0;for(int i = 1 ; i <= n ; ++i)for(int j = 1 ; j <= K ; ++j){f[i][j][0] = max(f[i-1][j][1] + w[i] , f[i-1][j][0]);f[i][j][1] = max(f[i-1][j][1],f[i-1][j-1][0] - w[i]); }int res = 0;for(int i = 0 ; i <= K ; ++i)res = max(res , f[n][i][0]);cout<<res<<endl;return 0 ; }

    股票買賣Ⅴ

    題目轉送門

    狀態機模型
    我們不能遺落任何一條轉移的邊

    代碼:

    #include<iostream> #include<algorithm> #include<cstring> using namespace std; const int N = 1E5 + 10; int n ; int w[N]; int f[N][3];int main(){cin>>n;for(int i = 1 ; i <= n ; ++i)cin>>w[i];memset(f , -0x3f , sizeof f);for(int i = 0 ; i <= n ;++i)f[i][0] = 0;for(int i = 1; i <= n ; ++i){f[i][0] = max(f[i-1][0] , f[i-1][2]);f[i][1] = max(f[i-1][0] - w[i] ,f[i-1][1]);f[i][2] = f[i-1][1] + w[i];}cout<<max(f[n][0] ,max(f[n][1] ,f[n][2]))<<endl;return 0; }

    KMP + 狀態機

    設計密碼

    題目鏈接

    解題思路:
    剛開始我的想法是f[i][j]f[i][j]f[i][j]表示第i位字符的時候,該字符是子串的字符和不是子串的字符的數量。不過這樣就有一個問題:f[i][1]f[i][1]f[i][1]表示第i位填的是子串中的字符,那么能轉移到它的狀態可以是f[i?1][0]和f[i?1][1]f[i-1][0] 和 f[i-1][1]f[i?1][0]f[i?1][1]如果是后者我們無法知道i-1位的是子串的哪一個字符,那么我們就無法知道能填的字符數量。因此我們需要增加維度,那么第二維的含義就很關鍵,如果僅僅表示的是子串中的字符,我們還是無法辨別是否與子串相同,那么我們就可以借助KMP中的f數組:的含義定義第二維,那么有(因為該題是與子串相關的,而KMP就是處理子串匹配的)


    代碼:

    #include<iostream> #include<algorithm> #include<cstring> using namespace std; const int N = 55 , mod = 1e9 + 7; int n; char s[N]; int f[N][N];int main(){cin>>n>>s+1;int len = strlen(s+1);int next[N] = {0};for(int i = 2 , j = 0 ; i <= len ; ++i){while(j && s[j+1] != s[i]) j = next[j];if(s[j+1] == s[i])j++;next[i] = j;}f[0][0] = 1;for(int i = 0; i <n ; ++i)for(int j = 0 ; j < len ; ++j)for(int k = 'a' ; k <= 'z' ; ++k){int u = j ; while(u && k != s[u+1]) u = next[u];if(s[u+1] == k)u++;if(u < len) f[i+1][u] = (f[i+1][u] + f[i][j]) % mod; }int res = 0;for(int i = 0 ; i < len ; ++i) res = (res + f[n][i]) % mod;cout<<res<<endl;return 0; }

    AC自動機 + 狀態機

    修復DNA

    題目轉送門

    解題思路:
    首先這個題和上一題的解題思路是類似的,其次為什么想到AC自動機呢,因為AC自動機就是解決多串匹配的問題

    至于動態規劃的過程我們類似上一題

    這題我們分為AC自動機的建立與動態規劃計算兩個部分來解決。

  • AC自動機的建立:1、依據每一個子串建立Trie樹(利用插入函數),2、從樹根開始向下依次計算出每一個節點的后綴節點 (建立AC自動機的過程)
  • 動態規劃狀態的計算
  • 代碼:

    #include<iostream> #include<algorithm> #include<cstring> #include<cstdio> using namespace std; const int N = 1010 , INF = 0x3f3f3f3f; int n , m; int tr[N][4] , dar[N] ,idx; //Trie int q[N] , pre[N]; //AC自動機 char str[N] ; int f[N][N];int get(char c){if(c == 'A')return 0;if(c == 'T')return 1;if(c == 'G')return 2;return 3; }void insert(){int p = 0;for(int i = 0 ; str[i] ; ++i){int k = get(str[i]);if(!tr[p][k])tr[p][k] = ++idx;p = tr[p][k]; }dar[p] = 1; }void build() {int hh = 0 , tt = -1;for(int i = 0 ; i < 4 ; ++i)if(tr[0][i]) q[++tt] = tr[0][i];while(hh <= tt){int t = q[hh++];for(int i = 0 ; i < 4 ;++i){int p = tr[t][i];if(!p) tr[t][i] = tr[pre[t]][i];else{pre[p] = tr[pre[t]][i];q[++tt] = p;dar[p] |= dar[pre[p]];}}} }int main(){int T = 1;while(cin>>n , n ){memset(tr , 0 ,sizeof tr);memset(dar , 0 , sizeof dar);memset(pre , 0 ,sizeof pre);idx = 0;for(int i = 1 ; i <= n ; ++i){cin>>str;insert();}build();//建立AC自動機cin>>str+1;m = strlen(str+1);memset(f , 0x3f ,sizeof f);f[0][0] = 0;for(int i = 0 ; i < m ; ++i)for(int j = 0 ; j <= idx ; ++j)for(int k = 0 ; k < 4 ; ++k){int t = get(str[i+1]) != k;int p = tr[j][k];if(!dar[p])f[i+1][p] = min(f[i+1][p] ,f[i][j] + t);}int res = INF;for(int i = 0 ; i <= idx ; ++i )res = min(res ,f[m][i]);if(res == INF)res = -1;printf("Case %d: %d\n",T++,res);}return 0; }

    總結

    以上是生活随笔為你收集整理的动态规划之状态机模型的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。