終於比較理解了數字 \(dp\) ( qwq
處理大數區間的計數,分成每一位考慮,\(f_\) 考慮從高到低位在第 \(pos\) 位並且滿足某些條件的答案,這個東西我們可以記憶化搜尋,但是注意要設計好狀態,不然會漏或者重複計算某些情況 qwq 。
需要觀察題目有沒有前導 \(0\) 限制,不過這個也不是重點了。
關鍵在於對狀態的設計。
[zjoi2010]數字計數
數字 \(dp\) 模板題。
設當前我們要統計的數為 \(g\),\(dp_\) 表示從高到低第 \(pos\) 位已經選了 \(sta\) 個 \(g\) 的方案數。直接 \(dp\) 即可。
int dfs(int pos,int sta,int lim,int zero)
if(!lim && !zero) dp[pos][sta]=sum;
return sum;
}
[scoi2009] windy 數
又乙個數字 \(dp\) 模板題。
設 \(dp_\) 為第 \(pos\) 位且上一位的數是 \(pre\) 時的答案,直接 \(dp\) 。注意當處於前導 \(0\) 狀態卻滿足條件時可以直接 \(continue\)。
int dfs(int pos,int pre,int lim,int zero)
if(!lim && !zero) dp[pos][pre+20]=sum;
return sum;
}
萌數
比起前兩道要難一些的數字 \(dp\) 題。
考慮乙個字串存在至少乙個子串是回文串的情況。顯然,這個子串的最短長度為 \(2\) 或 \(3\),所以我們可以考慮記錄在第 \(pos\) 位時,前兩位分別的值。然後你發現這連樣例都過不去...
考慮一種情況是 \(090\),這種情況你發現 \(pos-2\) 位置上是處於前導 \(0\) 狀態。所以我們還需要記錄處於第 \(pos\) 位時,前兩位是否處於前導 \(0\) 狀態。然後發現這個東西要開 \(6\) 維陣列...
\(dp_\) 記錄第 \(pos\) 位時,第 \(pos-1\) 位的數是 \(m1\),第 \(pos-2\) 位的數是 \(m2\),當前是否已經產生回文串 \(sta\),第 \(pos-1\) 位是否處於前導 \(0\) 狀態 \(qwq1\),第 \(pos-2\) 位是否處於前導 \(0\) 狀態 \(qwq2\) 。
int dfs(int pos,int sta,int m1,int m2,int qwq1,int qwq2,int lim,int zero)
*/ return sta;
}if((!lim) && (!zero) && (~dp[pos][m1][m2][sta][qwq1][qwq2])) return dp[pos][m1][m2][sta][qwq1][qwq2];
int sum=0, up=(lim)?(s[pos]-'0'):9;
for(ri int i=0;i<=up;i++)
if((!lim) && (!zero) ) dp[pos][m1][m2][sta][qwq1][qwq2]=sum;
return sum;
}
[cf55d] beautiful numbers
帶些思考的數字 \(dp\) 題。
考慮 \(1-9\) 區間的數的 \(lcm\) 最大為 \(2520\),所以你對於算出的那個數 \(x\),只需要考慮 \(x\%2520\) 即可。然後發現要開個差不多 \(20\times 2520 \times 2520\) 的陣列還要用好多次,會 \(mle\)...
觀察到 \(1-9\) 區間的數的 \(lcm\) 個數僅有 \(48\) 個,可以用類似於離散化一樣的思想預處理一下就好了。
inline void init()
int dfs(int pos,int sta,int now,int lim,int zero)
if((!lim) && (!zero) && (~dp[pos][sta][now])) return dp[pos][sta][now];
int sum=0, up=(lim)?s[pos]:9;
for(ri int i=0;i<=up;i++)
if((!lim) && (!zero)) dp[pos][sta][now]=sum;
return sum;
}
[ahoi2009]同類分布
原數的值這個狀態顯然存不下...考慮到上一題我們的做法,將這個原數對某個數進行取模。
發現各位數字之和的值域 \(x\) 非常小,設原數為 \(sta\),則我們要得到 \(sta\%x==0\)。所以我們可以列舉各位數字之和 \(x\),把 \(x\) 作為模數即可。
int dfs(int pos,int sta,int now,int mod,int lim,int zero)
if((!lim) && (!zero) && (~dp[pos][sta][now])) return dp[pos][sta][now];
int sum=0, up=(lim)?s[pos]:9;
for(ri int i=0;i<=up;i++) sum+=dfs(pos-1,(sta*10+i)%mod,now+i,mod,lim&&(i==up),zero&&(!i));
if((!lim) && (!zero)) dp[pos][sta][now]=sum;
return sum;
}inline int s(int k)
return res;
}
花神的數論題
考慮把原數先變為二進位制表示。然後列舉二進位制數的 \(1\) 的個數 \(i\),利用快速冪統計貢獻。
注意在數字 \(dp\) 求方案數時不能對 \(1e7+7\) 取模,因為它不是質數。
int dfs(int pos,int sta,int now,int lim,int zero)
inline int s(int k)
return res;
}
[cf628d] magic numbers
和之前題目一樣的套路,直接判斷第 \(i\) 位與 \(d\) 的關係即可。
int dfs(int pos,int sta,int lim,int zero)
else
}if((!lim) && (!zero)) dp[pos][sta]=sum;
return sum;
}
[cf401d] roman and numbers
直接狀壓 \(dp\) 即可。但是我還是用了個數字 \(dp\),一開始我是用 \(dp_\) 表示當前順序表示出來的數 \(\%\)
\(m\) 的值,當前選的數的集合(用二進位制狀壓表示)為 \(now\)。然後直接 \(tle\)。。。
這種做法最後還要對 \(0-9\) 的每個數出現次數 \(cnt\) 進行去重。所以你發現在數字 \(dp\) 時會非常慢。考慮進行乙個優化,對每個 \(0-9\) 的數在某位確定了之後,不再需要考慮相同的數編號互換的情況,我們可以把數字拆分後排序,這樣不會影響答案並可以去掉重複操作。
inline int dfs(int pos,int sta,int now)
dp[sta][now]=sum;
return sum;
}inline int s(int k)
[cqoi2016]手機號碼
直接暴力記錄 \(dp_\) 表示在第 \(pos\) 位,是否已經滿足條件 \(sta\),\(pos+1\) 位的數 \(m1\) 和 \(pos+2\) 位的數 \(m2\),是否出現過 \(4\) 用 \(t1\) 記錄,\(8\) 用 \(t2\) 記錄。直接暴力數字 \(dp\) 即可。注意當數字總位數不為 \(11\) 時直接返回 \(0\),因為我們在數字 \(dp\) 時可以直接跳過前導 \(0\)。
int dfs(int pos,int sta,int m1,int m2,int t1,int t2,int lim)
if((!lim) && (~dp[pos][sta][m1][m2][t1][t2])) return dp[pos][sta][m1][m2][t1][t2];
int sum=0;
int dn=(pos==cnt)?1:0;
int up=(lim)?s[pos]:9;
for(ri int i=dn;i<=up;i++)
if(!lim) dp[pos][sta][m1][m2][t1][t2]=sum;
return sum;
}inline int s(int k)
數字dp 板子題
題目傳送 存乙個狀態就可以了,用來判斷前一位是不是6的情況 具體看 注釋 ac include inline long long read while c 0 c 9 return x s using namespace std define newnode treenode malloc size...
數字dp題集
題集見大佬部落格 入門題,檢驗剛才自己有沒有看懂 注意一些細節。的確挺套路的 include define rep i,a,b for register int i a i b i define for i,a,b for register int i a i b i using namespace...
簡單數字dp
題目鏈結 題意 n,m 中不包含4和62的數的個數 include include include include using namespace std int dp 10 10 dp i j 表示最高位數字為i,長度為j的的數字串中滿足無4,無62的串的總數 void init 求得小於n的串中...