1. 程式人生 > 其它 >動態規劃-牛客NC21302(被3整除的子序列)

動態規劃-牛客NC21302(被3整除的子序列)

還是先上題目

連結:https://ac.nowcoder.com/acm/problem/21302
來源:牛客網

題目描述

給你一個長度為50的數字串,問你有多少個子序列構成的數字可以被3整除
答案對1e9+7取模

輸入描述:

輸入一個字串,由數字構成,長度小於等於50

輸出描述:

輸出一個整數
示例1

輸入

複製
132

輸出

複製
3
示例2

輸入

複製
9

輸出

複製
1
示例3

輸入

複製
333

輸出

複製
7
示例4

輸入

複製
123456

輸出

複製
23
示例5

輸入

複製
00

輸出

複製
3

備註:

n為長度
子任務1: n <= 5
子任務2: n <= 20
子任務3: 無限制


這道題的難點我認為在於怎麼找到這個字串的所有子串是否能整除3。考慮到這題要用DP,我覺得應該是從最後一個大串往前推的,但是沒有思路。
看了大佬的解析,理解了他的思路,附上轉載“划水摸魚的文同學”的解析:
首先題目中的子序列並不是連續的子序列,中間是可以有跳躍的。 例如字串“134”,它的子串就有{1,3,4,13,14(這個子串就是不連續的,中間跳過了一個3),134}, 然後我們設an為長度是n的字串的子串數量,為了方便理解,下面我舉幾個例子;
例:字串“3”,長度n=1,a1
=1; 字串“32”,長度n=2,a2=3; 字串“321”,長度你=3,a3=7; 可以看出an=an-1*2+1; 原理就是“32”的子串有{3,2,32}; 在增加了一位變成”321“後,子串變成了 {3,2,32,31,21,321,1} 不知道大家注意到沒有,加粗下劃線的字型其實就是在前一個子串集合裡的元素後面加上後加入的字元“1”,斜體下劃線的字型就是最後加上個新增加的字元“1”;
上面的例子主要是為了讓大家能更好的理解長度為n的字串與長度為n+1的字串之間子串的關係, 其實長度為n+1的字串它的子串就是包括了長度為n的字串的子串,並在n的每個子串後面加上一個 多出來的字元,再加上多出來的字元本身,那麼子串對3求餘的關係就如下面的例子
例: 字串“123”,子串{1,2,3,12,13,23,123} 對3求餘為0的子串:3,12,123;(數量為3) 對3求餘為1的子串:1,13;(數量為2) 對3求餘為2的子串:2,23;(數量為2) 然後在字串“123”的基礎上加一個字元,這個要根據這個字元對3求餘的不同情況進行分別討論, 例1:”1234“(加入了新字元“4”,對3求余余1) 對3求餘為0的子串:3,12,123,24
,234;(數量為3+2(這個2是上面對3求餘為2的子串加上新字元“4”後形成的新子串,因為之前的子串餘2,加上新字元對3求余余1,餘下的2+1等於3,剛好又對3求餘時餘0,下面的子串也同理)) 對3求餘為1的子串:1,13,34,124,1234,4(數量為2+3+1(3的來源可以參考上面2的來源,這裡就不細講了,1的來源就是新加入的字元“4”)) 對3求餘為2的子串:2,23,14,134(數量為2+2) 設bn,c為前n位字元對3求余余c的子串數量, 則可以總結出當新字元對3取余余1時 bn,0=bn-1,0+ bn-1,2 bn,1=bn-1,1+ bn-1,0 + 1 bn,2=bn-1, 2+bn-1,1 剩下的幾種情況我就不再這裡推導了,大家可以試著自己思考一下。
接下來就是動態規劃的內容了,設定一個數組來儲存計算過程中的資料date【50】【3】 date【50】【3】就是上面的例子bn,c 從第一個字元開始,計算對應求餘子串並記錄,然後讀入字串的下一位再用對應的公式對每一步 的計算結果進行儲存與呼叫就可以計算出答案了,記得對結果取1e9+7餘 下面附上我的程式碼,本人才開始接觸這類演算法題,有什麼不足的地方望大佬指出, 複製程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include<iostream> #include<cstring> //此題的子串並非連續子串,中間可有間隔; //還有對中間的計算過程的數字對N求餘,不然資料中間可能會超,導致答案出錯 constintN=1e9+7; usingnamespacestd; intmain() { stringa; cin>>a; intlenth=a.size(),m=0;longlongdate[50][3]={0}; m=(a[0]-'0')%3; date[0][m]=1; for(inti=1;i<lenth;i++) { m=(a[i]-'0')%3; switch(m) { //新字元餘0的情況 case0: { date[i][0]=(date[i-1][0]*2+1)%N; date[i][1]=(date[i-1][1]*2)%N; date[i][2]=(date[i-1][2]*2)%N; break; } //新字元餘1的情況 case1: { date[i][0]=(date[i-1][0]+date[i-1][2])%N; date[i][1]=(date[i-1][1]+date[i-1][0]+1)%N; date[i][2]=(date[i-1][2]+date[i-1][1])%N; break; } //新字元餘2的情況 case2: { date[i][0]=(date[i-1][0]+date[i-1][1])%N; date[i][1]=(date[i-1][1]+date[i-1][2])%N; date[i][2]=(date[i-1][2]+date[i-1][0]+1)%N; break; } } } cout<<date[lenth-1][0]%N;//輸出時取餘,按照題目要求 return0; }
這個程式碼非常易懂,解析也很明白。做完題後檢視別人程式碼,發現可以用巢狀for迴圈省略switch語句,
而在處理case[j]中date[i-1][j]=(......+1)的+1時,先採用了date[i][j]=1,再+=的思想,很美!
附上優化程式碼:(程式碼來自
華夏Power
/*優化演算法,避開switch,使用一個dp[i][m]=1做到原先的casej,box[i][j]+1 #include<bits/stdc++.h> #definemaxn500006 #defineinf0x3f3f3f3f3f3f3f3f//什麼意思? #definemod1000000007 #definePiacos(-1)//什麼意思? usingnamespacestd; typedeflonglongll; typedefpair<ll,string>P;//什麼? lldp[51][3]; intmain(){ ios::sync_with_stdio(false); strings; cin>>s; dp[0][(s[0]-'0')%3]=1; for(lli=1;i<s.size();i++){ llm=(s[i]-'0')%3; dp[i][m]=1; for(llj=0;j<3;j++){ dp[i][j]+=(dp[i-1][j]+dp[i-1][(j-m+3)%3])%mod; } } cout<<dp[s.size()-1][0]%mod<<endl; return0; } */
但是有些地方還是看不懂這麼寫的意義,希望有大佬能評論回覆一下,學習一下!