1. 程式人生 > 實用技巧 >簡單基礎數位 dp 題

簡單基礎數位 dp 題

終於比較理解了數位 \(dp\) ( qwq

處理大數區間的計數,分成每一位考慮,\(f_{pos}\) 考慮從高到低位在第 \(pos\) 位並且滿足某些條件的答案,這個東西我們可以記憶化搜尋,但是注意要設計好狀態,不然會漏或者重複計算某些情況 qwq 。

需要觀察題目有沒有前導 \(0\) 限制,不過這個也不是重點了。

關鍵在於對狀態的設計。


[ZJOI2010]數字計數

數位 \(dp\) 模板題。

設當前我們要統計的數為 \(G\)\(dp_{pos,sta}\) 表示從高到低第 \(pos\) 位已經選了 \(sta\)\(G\) 的方案數。直接 \(dp\) 即可。

int DFS(int pos,int sta,int lim,int zero)
{// lim 表示最高位限制,zero 表示是否在前導零
    if(!pos) return sta;
    if(!lim && !zero && (~dp[pos][sta])) return dp[pos][sta];
    int sum=0, up=(lim)?s[pos]:9;
    for(ri int i=0;i<=up;i++)
    {
        int pp=zero&&(!i);
        sum+=DFS(pos-1,sta+((!pp)&&i==G),lim&&(i==up),zero&&(!i));//如果不在前導零才可以貢獻,否則當統計 0 的貢獻時候會炸掉。
    }
    if(!lim && !zero) dp[pos][sta]=sum;
    return sum;
}

[SCOI2009] windy 數

又一個數位 \(dp\) 模板題。

\(dp_{pos,pre}\) 為第 \(pos\) 位且上一位的數是 \(pre\) 時的答案,直接 \(dp\) 。注意當處於前導 \(0\) 狀態卻滿足條件時可以直接 \(continue\)

int DFS(int pos,int pre,int lim,int zero)
{
    if(!pos) return 1;
    if((!lim) && (!zero) && (~dp[pos][pre+20])) return dp[pos][pre+20];
    int sum=0, up=(lim)?s[pos]:9;
    for(ri int i=0;i<=up;i++)
    {
        if(abs(i-pre)<2&&(!zero)) continue;
        sum+=DFS(pos-1,i,lim&&(i==up),zero&&(!i));
    }
    if(!lim && !zero) dp[pos][pre+20]=sum;
    return sum;
}

萌數

比起前兩道要難一些的數位 \(dp\) 題。

考慮一個字串存在至少一個子串是迴文串的情況。顯然,這個子串的最短長度為 \(2\)\(3\),所以我們可以考慮記錄在第 \(pos\) 位時,前兩位分別的值。然後你發現這連樣例都過不去...

考慮一種情況是 \(090\),這種情況你發現 \(pos-2\) 位置上是處於前導 \(0\) 狀態。所以我們還需要記錄處於第 \(pos\) 位時,前兩位是否處於前導 \(0\) 狀態。然後發現這個東西要開 \(6\) 維陣列...

\(dp_{pos,m1,m2,sta,qwq1,qwq2}\) 記錄第 \(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)
{
    if(!pos)
    {
        /*if(sta)
        {
            for(ri int i=len;i;i--) printf("%lld",cnt[i]);
            puts("");
        }*/
        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++)
    {
        int qq=(((m1==i)&&(!qwq1))||((m2==i)&&(!qwq2)))||sta;
        int pp=(zero&&(!i));
        cnt[pos]=i;
        sum=(sum+DFS(pos-1,qq,i,m1,pp,qwq1,lim&&(i==up),pp))%Mod;
        cnt[pos]=0;
    }
    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()
{
    for(ri int a=0;a<=3;a++)
    for(ri int b=0;b<=2;b++)
    for(ri int c=0;c<=1;c++)
    for(ri int d=0;d<=1;d++)
    g[++cnt]=ksc(2,a)*ksc(3,b)*ksc(5,c)*ksc(7,d);
    sort(g+1,g+1+cnt);
    for(ri int i=1;i<=cnt;i++) book[g[i]]=i;
    pw[0]=1;
    for(ri int i=1;i<=18;i++) pw[i]=pw[i-1]*10;
}
int DFS(int pos,int sta,int now,int lim,int zero)
{
    if(!pos)
    {
        if(sta%g[now]) return 0;
        return 1;
    }
    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++)
    {
        int pp=(sta+i*pw[pos-1]%Mod)%Mod;
        int qq=g[now];
        if(i) qq=qq*i/__gcd(i,qq);
        qq=book[qq];
        sum+=DFS(pos-1,pp,qq,lim&&(i==up),zero&&(!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(!pos)
    {
        if((!sta) && now==Mod) return 1;
        return 0;
    }
    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)
{
    int cnt=0;
    while(k) s[++cnt]=k%10, k/=10;
    int res=0;
    for(ri int i=1;i<=cnt*9;i++)
    {
        memset(dp,-1,sizeof(dp));
        res+=DFS(cnt,0,0,i,1,1);
    }
    return res;
}

花神的數論題

考慮把原數先變為二進位制表示。然後列舉二進位制數的 \(1\) 的個數 \(i\),利用快速冪統計貢獻。

注意在數位 \(dp\) 求方案數時不能對 \(1e7+7\) 取膜,因為它不是質數。

int DFS(int pos,int sta,int now,int lim,int zero)
{
    if(!pos) return (now==sta);
    if((!lim) && (!zero) && (~dp[pos][sta])) return dp[pos][sta];
    int sum=0, up=(lim)?s[pos]:1;
    for(ri int i=0;i<=up;i++) sum=(sum+DFS(pos-1,sta+i,now,lim&&(i==up),zero&&(!i)));
    if((!lim) && (!zero)) dp[pos][sta]=sum;
    return sum;
}
inline int S(int k)
{
    int cnt=0;
    while(k) s[++cnt]=(k&1ll), k>>=1ll;
    int res=1;
    for(ri int i=1;i<=cnt;i++)
    {
        memset(dp,-1,sizeof(dp));
        res=res*ksc(i,DFS(cnt,0,i,1,1))%Mod;
    }
    return res;
}

[CF628D] Magic Numbers

和之前題目一樣的套路,直接判斷第 \(i\) 位與 \(d\) 的關係即可。

int DFS(int pos,int sta,int lim,int zero)
{
    if(!pos) return (!sta);
    if((!lim) && (!zero) && (~dp[pos][sta])) return dp[pos][sta];
    int sum=0, up=(lim)?(s[pos]-'0'):9;
    for(ri int i=0;i<=up;i++)
    {
        if((len-pos+1)&1ll)
        {
            if(i==d) continue;
            sum=(sum+DFS(pos-1,(sta*10+i)%m,lim&&(i==up),zero&&(!i)))%Mod;
        }
        else
        {
            if(i^d) continue;
            sum=(sum+DFS(pos-1,(sta*10+i)%m,lim&&(i==up),zero&&(!i)))%Mod;
        }
    }
    if((!lim) && (!zero)) dp[pos][sta]=sum;
    return sum;
}