1. 程式人生 > >Segment Sum CodeForces - 1073E (經典數位dp統計和問題)

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);
}