1. 程式人生 > >codeforces 55d (lcm+數位dp)附板子

codeforces 55d (lcm+數位dp)附板子

就是學習的人家的,很久不寫,數位dp也忘記了咋寫。

程式碼如下:

注意dp只需要初始化時更新一次就好,

dp[i][j][k] 表示,右數第 i 位 ,mod為 j 時,lcm為k 的符合要求數目, 此處把lcm離散化了一下,

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <string>
#include <map>
#include <vector>

using namespace std;
const int MOD = 2520;
long long dp[30][2525][50];
int num[20];
int has[3000];
int s[3000];
int gcd(int a,int b)
{
    if(!b) return a;
    else
        return gcd(b,a%b);
}
int getlcm(int a,int b)
{
     int d = gcd(a,b);
     return (a/d)*b;
}
long long dfs(int pos,int mod,int lcm,int limit)
{
    if(pos<0) return mod%lcm==0;
    if(!limit && dp[pos][mod][has[lcm]]!=-1)
        return dp[pos][mod][has[lcm]];
    int tlcm;
    int endi = 9;
    if(limit)
        endi = num[pos];
    long long res = 0;
    for(int i=0; i<=endi; i++)
    {
        if(!i) tlcm = lcm;
        else  tlcm = getlcm(i,lcm);
        res +=dfs(pos-1,(mod*10+i)%MOD,tlcm,limit&&(i==endi));
    }
    if(!limit) dp[pos][mod][has[lcm]] = res;
    return res;

}
long long solve(long long n)
{
    int cnt = 0;
    while(n>0)
    {
        num[cnt++] = n%10;
        n/=10;
    }
  
    return dfs(cnt-1,0,1,1);
}
void init()
{
    memset(has,0,sizeof(has));
    int l = 0;
    for(int i=0; i<(1<<9);i++)
    {
        int now = 1;
        for(int j=1;j<9;j++)
        {
            if(i&(1<<j))
            {
                now = getlcm(now,j+1);
            }
        }
        has[now] = 1;
    }
    for(int i=0; i<=MOD; i++)
    {
        if(has[i])
        {
            s[l]= i;
            has[i] = l++;
        }
    }
}
int main()
{
      memset(dp,-1,sizeof(dp));
   int t;
   cin>>t;
   init();
   while(t--)
   {
       long long l,r;
       cin>>l>>r;
       long long ans1 = solve(l-1);
       long long ans2 = solve(r);
       cout<<ans2-ans1<<endl;
      // cout<<getlcm(l,r)<<endl;
   }
    return 0;
}

再接一個hls的數位dp板子


typedef long long ll;
int a[20];
ll dp[20][state];//不同題目狀態不同
ll dfs(int pos,/*state變數*/,bool lead/*前導零*/,bool limit/*數位上界變數*/)//不是每個題都要判斷前導零
{
    //遞迴邊界,既然是按位列舉,最低位是0,那麼pos==-1說明這個數我列舉完了
    if(pos==-1) return 1; 
           /*這裡一般返回1,表示你列舉的這個數是合法的,那麼這裡就需要你在列舉時必須每一位都要滿足題目條件,
          也就是說當前列舉到pos位,一定要保證前面已經列舉的數位是合法的。不過具體題目不同或者寫法不同的話不一定要返回1 */

    //第二個就是記憶化(在此前可能不同題目還能有一些剪枝)
    if(!limit && !lead && dp[pos][state]!=-1) return dp[pos][state];
    /*常規寫法都是在沒有限制的條件記憶化,這裡與下面記錄狀態是對應,具體為什麼是有條件的記憶化後面會講*/

    int up=limit?a[pos]:9;//根據limit判斷列舉的上界up;這個的例子前面用213講過了
    ll ans=0;

    //開始計數
    for(int i=0;i<=up;i++)//列舉,然後把不同情況的個數加到ans就可以了
    {
        if() ...
        else if()...
        ans+=dfs(pos-1,/*狀態轉移*/,lead && i==0,limit && i==a[pos]) //最後兩個變數傳參都是這樣寫的
        /*這裡還算比較靈活,不過做幾個題就覺得這裡也是套路了
        大概就是說,我當前數位列舉的數是i,然後根據題目的約束條件分類討論
        去計算不同情況下的個數,還有要根據state變數來保證i的合法性,比如題目
        要求數位上不能有62連續出現,那麼就是state就是要儲存前一位pre,然後分類,
        前一位如果是6那麼這意味就不能是2,這裡一定要儲存列舉的這個數是合法*/
    }
    //計算完,記錄狀態
    if(!limit && !lead) dp[pos][state]=ans;
    /*這裡對應上面的記憶化,在一定條件下時記錄,保證一致性,當然如果約束條件不需要考慮lead,這裡就是lead就完全不用考慮了*/
    return ans;
}
ll solve(ll x)
{
    int pos=0;
    while(x)//把數位都分解出來
    {
        a[pos++]=x%10;//個人老是喜歡編號為[0,pos),看不慣的就按自己習慣來,反正注意數位邊界就行
        x/=10;
    }
    return dfs(pos-1/*從最高位開始列舉*/,/*一系列狀態 */,true,true);
   //剛開始最高位都是有限制並且有前導零的,顯然比最高位還要高的一位視為0嘛
}
int main()
{
    ll le,ri;
    while(~scanf("%lld%lld",&le,&ri))
    {
        //初始化dp陣列為-1,這裡還有更加優美的優化,後面講
        printf("%lld\n",solve(ri)-solve(le-1));
    }
}