Segment Sum CodeForces - 1073E (經典數位dp統計和問題)
題意:給出l,r求出區間裡,滿足不同數的個數小於等於k的數的和。
思路:先解決第一個問題:如何統計不同數的個數?思路很簡單,因為只有0到9這10個數字,每出現一個新數字,將其用二進位制狀態表示出來,那麼我們只要統計最後狀態即可知道有多少個不同的數字。
第二個問題:如何計算和? 首先一個錯誤的思路會這樣想,dp[pos][sta]表示列舉到pos位時,當前狀態為sta的滿足條件的數的和,也就是每次列舉到pos==-1位時,返回滿足的數num或0。這樣只能跑對第一組樣例,原因很簡單,我們是記憶化搜尋,dp[pos][sta]
當搜尋過1234和4213時,sta記錄的量是相同的,所以發生了狀態重複問題。
也就是說,我們採用dp[pos][sta]的狀態表示,我們不能直接得出和,我們只能找出有sta這種狀態的數有多少個。這裡可以思考下,當我們搜尋過1234 後,dp[pos][sta]已經被我們記錄下來(sta=1111),當我們再次搜尋1243時,就可以直接利用記憶化結果,因為1234滿足,那麼1243也一定滿足。
所以我們採用的思路就是先統計滿足狀態的個數,最後再考慮如何進行計算。
對於這樣數位dp的統計和問題,套路就是按位計算貢獻。
比如,我們數位進行到12時,我們考慮2對最後結果的貢獻,前面的12已經確定了,我們還要考慮2到底會出現多少次,也就是說,如果後面是 34,35,36......這些數都滿足題意,那麼我們的2的貢獻必然是2*pow[pos]*cnt,cnt為後面到底還有多少個滿足題意。這樣就可以解決求和問題。
第三個問題:前導0問題。
只要是和統計各位上數的個數問題,都必須考慮前導0,原因是這樣的,如果我們列舉0012,很明顯,實際的數只是12,如果我們沒有關注前導0,那麼我們會把0也當成一個新的數,進而統計了0012有3個不同的陣列成。
解決方案就是,dfs的同時利用zero來記錄是否有前導0,如果有前導0,並且當前位還是0,那麼就說明現在的數還是0,不用考慮。
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int p = 998244353; int k, a[20], pw[20]; struct pr { int tot, sum; pr() {} pr(int a, int b) : tot(a), sum(b) {} void clr() { tot = sum = 0; } bool chk() { return ~tot && ~sum; } } dp[20][1 << 10]; void add(pr& a, pr b, int num, int pos) { a.tot = (a.tot + b.tot) % p; a.sum = (a.sum + 1ll * num * pw[pos] % p * b.tot % p + b.sum) % p;//類似統計字首和 } pr dfs(bool lim, bool zero, int pos, int mark) { if (!pos) return pr(1, 0); pr& res = dp[pos][mark]; if (lim==0&&res.chk()) return res; res.clr(); for (int i = 0; i <= (lim ? a[pos] : 9); i++) { if (__builtin_popcount(zero && !i ? 0 : mark | 1 << i) <= k) { add(res, dfs(lim && i == a[pos], zero && !i, pos - 1, zero && !i ? 0 : mark | 1 << i), i, pos - 1); } } return res; } int calc(ll x) { int len = 0; __builtin_memset(dp,-1,sizeof(dp)); //memset(dp, -1, sizeof dp); while (x) a[++len] = x % 10, x /= 10; return dfs(1, 1, len, 0).sum; } int main() { #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); freopen("out.txt","w",stdout); #endif pw[0] = 1; for (int i = 1; i < 19; i++) { pw[i] = 10ll * pw[i - 1] % p; } ll l, r; scanf("%lld %lld %d", &l, &r, &k); return 0*printf("%d", (calc(r) - calc(l - 1) + p) % p); }