• 数位dp总结


    补题的时候有个题需要数位dp,有去重学了一波上一年就学过的数位dp,又学一遍感觉上一年学了个寂寞,,,

    这种问题大多数都是和数的每一位的数字有关,一般是一个数的数位之间存在着某种关系,让求具有这种关系的数字在[a,b]的范围内有多少个。

    一般代码有两种,一种是偏递推的dp,另一种是记忆化搜索,前者太难了就放弃了,,,

    先按一道例题来说

    P2602 [ZJOI2010] 数字计数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

    一般的记忆化搜索的框架

    1. int dfs(int pos,int limit,int lead,int dig,int sum)
    2. {
    3. int ans=0;
    4. if(pos==0) return sum;
    5. if(!limit&&lead&&dp[pos][sum]) return dp[pos][sum];
    6. int up=limit?num[pos]:9;
    7. for(int i=0;i<=up;i++)
    8. {
    9. ans+=dfs(pos-1,(i==up)&&limit,i||lead,dig,sum+((i||lead)&&i==dig));
    10. }
    11. if(!limit&&lead) dp[pos][sum]=ans;
    12. return ans;
    13. }

    pos代表的是枚举到了数字的第几位,一般是先从高位枚举的;limit表示该位的最大值是9还是只能取该数字的这一位的值,比如3425,我们第一位枚举0~2的时候,第二位可以选0-9,但是如果我们第一位是3的话,那么第二位可以选0~4,依次推下去,如果第1位是3,第二位是4,那么第三位只能选0-2...

    lead表示有没有前导零,这个条件需要根据题目来设;dig是要枚举的数位,sum是前几位出现了几次dig,我们保存pos,sum的状态就可以进行转移了

    对于一个数a来说,进行处理前要把a的数位拆到数组里,然后用上面的dfs就可以求出每个数位从0-a出现了多少次了,对于一个区间[a,b]每个数位出现了多少次那自然就是每个数位从0-b出现的次数减去从0-(a-1)出现的次数

    题解代码

    1. #include
    2. #define int long long
    3. #define lowbit(x) ((x)&(-x))
    4. #define endl '\n'
    5. #define double long double
    6. using namespace std;
    7. const int mod=1e9+7;
    8. const int inf=3e18;
    9. double qpow(double a,int b)
    10. {
    11. double res=1;
    12. while(b)
    13. {
    14. if(b&1) res=res*a;
    15. a=a*a;
    16. b>>=1;
    17. }
    18. return res;
    19. }
    20. const int N=404;
    21. int dp[20][20],num[20];
    22. int dfs(int pos,int limit,int lead,int dig,int sum)
    23. {
    24. int ans=0;
    25. if(pos==0) return sum;
    26. if(!limit&&lead&&dp[pos][sum]) return dp[pos][sum];
    27. int up=limit?num[pos]:9;
    28. for(int i=0;i<=up;i++)
    29. {
    30. ans+=dfs(pos-1,(i==up)&&limit,i||lead,dig,sum+((i||lead)&&i==dig));
    31. }
    32. if(!limit&&lead) dp[pos][sum]=ans;
    33. return ans;
    34. }
    35. int sol(int x,int dig)
    36. {
    37. for(int i=0;i<=15;i++) num[i]=0;
    38. int len=0;
    39. while(x)
    40. {
    41. num[++len]=x%10;
    42. x/=10;
    43. }
    44. return dfs(len,1,0,dig,0);
    45. }
    46. signed main()
    47. {
    48. // cin.tie(0);
    49. // cout.tie(0);
    50. // ios::sync_with_stdio(0);
    51. //freopen("in.txt", "r", stdin);
    52. int a,b;
    53. cin>>a>>b;
    54. for(int i=0;i<=9;i++)
    55. {
    56. cout<<sol(b,i)-sol(a-1,i)<<" ";
    57. }
    58. system("pause");
    59. return 0;
    60. }

    P2657 [SCOI2009] windy 数

    不含前导零且相邻两个数字之差至少为 2 的正整数被称为 windy 数。

    可以看一下搜索的时候答案是与什么有关,比如搜到了第pos位,那么第pos位就与上一位是有关的(也与下一位有关,但是到了第pos+1位,那么第pos位也会用到),枚举该位可能出现的数的时候就看看是不是与上一位相差至少是2,如果枚举到第pos位,上一位是dig时,发现之前已经搜过了,那么就直接返回即可

    1. #include
    2. #define int long long
    3. #define lowbit(x) ((x)&(-x))
    4. #define endl '\n'
    5. #define double long double
    6. using namespace std;
    7. const int mod=1e9+7;
    8. const int inf=3e18;
    9. double qpow(double a,int b)
    10. {
    11. double res=1;
    12. while(b)
    13. {
    14. if(b&1) res=res*a;
    15. a=a*a;
    16. b>>=1;
    17. }
    18. return res;
    19. }
    20. int dp[22][22],num[22];
    21. int dfs(int pos,int limit,int lead,int dig)
    22. {
    23. if(pos==0) return 1;
    24. if(!limit&&lead&&dp[pos][dig]) return dp[pos][dig];
    25. int ans=0;
    26. int up=limit?num[pos]:9;
    27. for(int i=0;i<=up;i++)
    28. {
    29. if(abs(i-dig)<2&&lead) continue;
    30. ans+=dfs(pos-1,limit&&(i==up),lead||i,i);
    31. }
    32. if(!limit&&lead) dp[pos][dig]=ans;
    33. return ans;
    34. }
    35. int sol(int x)
    36. {
    37. memset(num,0,sizeof(num));
    38. int len=0;
    39. while(x) num[++len]=x%10,x/=10;
    40. return dfs(len,1,0,0);
    41. }
    42. signed main()
    43. {
    44. cin.tie(0);cout.tie(0);ios::sync_with_stdio(0);
    45. //freopen("in.txt", "r", stdin);
    46. memset(dp,0,sizeof(dp));
    47. int a,b;
    48. cin>>a>>b;
    49. cout<<sol(b)-sol(a-1)<
    50. return 0;
    51. }

    P4317 花神的数论题 

    这题是关于二进制的,这次是要把数按二进制的形式拆到数组里,然后看看第pos位的答案是与什么有关,可以发现只要知道了枚举到第pos位然后这一位的sum之前出现过,就说明这个状态之前搜过了,就可以直接返回了,sum表示的是第pos位之前1出现的个数;

    这题打的蒙蒙的,但没想到竟然过了

    1. #include
    2. #define int long long
    3. #define lowbit(x) ((x)&(-x))
    4. #define endl '\n'
    5. #define double long double
    6. using namespace std;
    7. const int mod=10000007;
    8. const int inf=3e18;
    9. double qpow(double a,int b)
    10. {
    11. double res=1;
    12. while(b)
    13. {
    14. if(b&1) res=res*a;
    15. a=a*a;
    16. b>>=1;
    17. }
    18. return res;
    19. }
    20. int dp[65][65],bit[65];
    21. int dfs(int pos,int limit,int sum)
    22. {
    23. if(pos==0) return sum;
    24. if(!limit&&dp[pos][sum]) return dp[pos][sum];
    25. int up=limit?bit[pos]:1,ans=1;
    26. for(int i=0;i<=up;i++)
    27. {
    28. int tmp=dfs(pos-1,limit&&(i==up),sum+i);
    29. if(!tmp) continue;
    30. ans=ans*tmp%mod;
    31. }
    32. if(!limit) dp[pos][sum]=ans;
    33. return ans;
    34. }
    35. int sol(int x)
    36. {
    37. memset(bit,0,sizeof(bit));
    38. int len=0;
    39. while(x) bit[++len]=x%2,x/=2;
    40. return dfs(len,1,0);
    41. }
    42. signed main()
    43. {
    44. cin.tie(0);cout.tie(0);ios::sync_with_stdio(0);
    45. //freopen("in.txt", "r", stdin);
    46. int n;
    47. cin>>n;
    48. cout<<sol(n)<
    49. return 0;
    50. }

    P6218 [USACO06NOV] Round Numbers S

    这个题目其实而上一题差不多,都是二进制,但这个题需要考虑前导零了,因为1的二进制形式是1而不是01,如果是01那就错了,之后按照题目条件来判断就行

    1. #include
    2. #define int long long
    3. #define lowbit(x) ((x)&(-x))
    4. #define endl '\n'
    5. #define double long double
    6. using namespace std;
    7. const int mod=10000007;
    8. const int inf=3e18;
    9. double qpow(double a,int b)
    10. {
    11. double res=1;
    12. while(b)
    13. {
    14. if(b&1) res=res*a;
    15. a=a*a;
    16. b>>=1;
    17. }
    18. return res;
    19. }
    20. int bit[65];
    21. mapint,int>,int>dp;
    22. int dfs(int pos,int limit,int lead,int sum)
    23. {
    24. if(pos==0) return sum>=0;
    25. if(!limit&&lead&&dp[{pos,sum}]) return dp[{pos,sum}];
    26. int up=limit?bit[pos]:1,ans=0;
    27. for(int i=0;i<=up;i++)
    28. {
    29. int x;
    30. if(i==0)
    31. {
    32. if(lead) x=1;
    33. else x=0;
    34. }
    35. else x=-1;
    36. ans+=dfs(pos-1,limit&&(i==up),lead||i,sum+x);
    37. }
    38. if(!limit&&lead) dp[{pos,sum}]=ans;
    39. return ans;
    40. }
    41. int sol(int x)
    42. {
    43. memset(bit,0,sizeof(bit));
    44. int len=0;
    45. while(x) bit[++len]=x%2,x/=2;
    46. return dfs(len,1,0,0);
    47. }
    48. signed main()
    49. {
    50. cin.tie(0);cout.tie(0);ios::sync_with_stdio(0);
    51. //freopen("in.txt", "r", stdin);
    52. int a,b;
    53. cin>>a>>b;
    54. cout<<sol(b)-sol(a-1)<
    55. return 0;
    56. }

    P2106 Sam数 - 数位dp,矩阵快速幂

    f[i][j]为第i位上数字为j的个数,那么第i+1位可以取的值就有j-2,j-1,j,j+1,j+2,也就是

    f[i+1][j]+=f[i][j-2]+f[i][j-1]+f[i][j]+f[i][j+1]+f[i][j+2];

    i太大用矩阵快速幂递推,可以做到log级,f[i+1][0]=f[i][0]+f[i][1]+f[i][2],f[i+1][1]=f[i][0]+f[i][1]+f[i][2]+f[i][3],这样其实也就可以把构造矩阵B看出来了,然后在把f[1]的初始矩阵A推出来就可以快速幂了,之后统计0~9的答案和就可以了,k=1的时候特判一下

    1. #include
    2. #define int long long
    3. #define lowbit(x) ((x)&(-x))
    4. #define endl '\n'
    5. #define double long double
    6. using namespace std;
    7. const int mod=1000000007;
    8. const int inf=1e9-1;
    9. const int N=1e7+7;
    10. double qpow(double a,int b)
    11. {
    12. double res=1;
    13. while(b)
    14. {
    15. if(b&1) res=res*a;
    16. a=a*a;
    17. b>>=1;
    18. }
    19. return res;
    20. }
    21. bool cmp(int &a,int &b)
    22. {
    23. return a>b;
    24. }
    25. struct Matrix
    26. {
    27. int mat[12][12];
    28. Matrix()
    29. {
    30. memset(mat,0,sizeof(mat));
    31. }
    32. Matrix operator*(const Matrix &a)
    33. {
    34. Matrix res;
    35. memset(&res,0,sizeof(res));
    36. for(int i=0;i<=9;i++)
    37. for(int j=0;j<=9;j++)
    38. for(int k=0;k<=9;k++)
    39. res.mat[i][j]=(res.mat[i][j]+mat[i][k]*a.mat[k][j]%mod)%mod;
    40. return res;
    41. }
    42. };
    43. Matrix qpow(Matrix a,int b)
    44. {
    45. Matrix res;
    46. memset(&res,0,sizeof(res));
    47. for(int i=0;i<=9;i++) res.mat[i][i]=1;
    48. while(b)
    49. {
    50. if(b&1) res=res*a;
    51. a=a*a;
    52. b>>=1;
    53. }
    54. return res;
    55. }
    56. int n;
    57. signed main()
    58. {
    59. cin.tie(0);cout.tie(0);ios::sync_with_stdio(0);
    60. //freopen("in.txt", "r", stdin);
    61. cin>>n;
    62. if(n==1)
    63. {
    64. cout<<"10\n";return 0;
    65. }
    66. Matrix a,b;
    67. for(int i=1;i<=9;i++) a.mat[i][0]=1;
    68. for(int i=0;i<=9;i++)
    69. for(int j=max(i-2,0LL);j<=min(9LL,i+2);j++)
    70. b.mat[i][j]=1;
    71. b=qpow(b,n-1);
    72. int ans=0;
    73. a=b*a;
    74. for(int i=0;i<=9;i++) ans=(ans+a.mat[i][0])%mod;
    75. cout<
    76. return 0;
    77. }

    P2481 [SDOI2010]代码拍卖会 - 数位dp

    对于任意一个数x,我们总是可以用类似111...111的数来组成,比如

    (来自题解 P2481 【[SDOI2010]代码拍卖会】 - ​ - 洛谷博客)

     而且可以发现对于模数p,1,11,111,111...111这样的数在模p意义下是会有循环节的,设g[i]表示所有有多少类似111...111这样的数模p等于i,然后问题就转化成了从g数组里选不超过9个数模p等于0的方案数,dp[i][j][k]表示前i个数选了k个数这些数的总和模p为j的方案数,假设从g[i+1]中选了o个,那么转移就是dp[i+1][(j+k*i)%p][k+o]+=dp[i][j][k]*c[i][k],c[i][k]表示从g[i]中可重复的选k个,公式就是(kg[i]+k1)" role="presentation" style="position: relative;">(kg[i]+k1),可以直接算也可以递推,递推在有些时候耗时更少点,所以代码就先用递推了

    用到的点:把数拆成111...111相加的形式,模p意义下有很多数是相同的,循环节,dp计数

    题解 P2481 【[SDOI2010]代码拍卖会】 - 瓶中水 - 洛谷博客

    1. #include
    2. #define int long long
    3. #define lowbit(x) ((x)&(-x))
    4. #define endl '\n'
    5. #define double long double
    6. using namespace std;
    7. const int mod=999911659;
    8. const int inf=1e9-1;
    9. const int N=1e6+7;
    10. int qpow(int a,int b)
    11. {
    12. int res=1;
    13. while(b)
    14. {
    15. if(b&1) res=res*a%mod;
    16. a=a*a%mod;
    17. b>>=1;
    18. }
    19. return res;
    20. }
    21. int getinv(int a){return qpow(a,mod-2);}
    22. int n,p,g[505],a[505],f[505][505][12],inv[12],c[505][12];
    23. int vn;
    24. signed main()
    25. {
    26. //cin.tie(0);cout.tie(0);ios::sync_with_stdio(0);
    27. //freopen("in.txt", "r", stdin);
    28. cin>>n>>p;inv[1]=1;
    29. if(n<=p) for(int i=1;i<=n;i++) vn=(vn*10+1)%p,g[vn]++;
    30. else
    31. {
    32. int tot=1%p,l,m;
    33. for(int i=1;i<=p+1;i++)
    34. {
    35. if(a[tot]){l=a[tot];m=i-l;break;}//找到循环节,记录位置
    36. a[tot]=i;g[tot]++;tot=(tot*10+1)%p;//这个数出现的坐标
    37. }
    38. for(int i=0;i
    39. {
    40. if(a[i]&&a[i]>=l)
    41. {
    42. g[i]=(g[i]+(n-a[i])/m)%mod;//加上后面的循环节的个数
    43. if((a[i]-l+1)%m==(n-l+1)%m) vn=i;//找到与n个1模p的数
    44. }
    45. }
    46. }
    47. for(int i=2;i<=9;i++) inv[i]=(mod-mod/i)*inv[mod%i]%mod;
    48. for(int i=0;i
    49. {
    50. c[i][0]=1;
    51. if(!g[i]) continue;
    52. for(int j=1;j<9;j++,g[i]=(g[i]+1)%mod)
    53. c[i][j]=((c[i][j-1]*g[i]%mod)*inv[j])%mod;
    54. }
    55. f[0][vn][0]=1;
    56. for(int i=0;i
    57. for(int j=0;j
    58. for(int o=0;o<9;o++)
    59. for(int k=0;k<9-o;k++)
    60. f[i+1][(j+k*i%p)%p][k+o]=(f[i+1][(j+k*i%p)%p][k+o]+f[i][j][o]*c[i][k]%mod)%mod;
    61. int ans=0;
    62. for(int i=0;i<9;i++) ans=(ans+f[p][0][i])%mod;
    63. cout<
    64. return 0;
    65. }

    CF855E Salazar Slytherin's Locket - 数位dp

    和模板差不多,需要注意的就是最多需要记录十个位置的状态,每个状态要么是奇数要么是偶数,那就可以用2进制来表示,也就是状态压缩一下,sta就表示十个数的状态,最后返回的时候就是看sta是不是0就可以

    CF855E题解 - Ginger_he 的博客 - 洛谷博客

    1. #include
    2. using namespace std;
    3. #define ll long long
    4. const int N = 1e5+100;
    5. const double eps=1e-8;
    6. const double pi=acos(-1);
    7. int num[65],q,b,len;
    8. ll f[12][65][1024],l,r;
    9. ll dfs(int pos,int limit,int lead,int sta)
    10. {
    11. if(pos==0) return !sta;
    12. if(!limit&&lead&&f[b][pos][sta]!=-1) return f[b][pos][sta];
    13. ll res=0,up=limit?num[pos]:(b-1);
    14. for(int i=0;i<=up;i++)
    15. {
    16. res+=dfs(pos-1,(i==up)&&limit,i||lead,(i||lead)?(sta^(1<0);
    17. }
    18. if(!limit&&lead) f[b][pos][sta]=res;
    19. return res;
    20. }
    21. ll sol(ll x)
    22. {
    23. len=0;
    24. while(x) num[++len]=x%b,x/=b;
    25. return dfs(len,1,0,0);
    26. }
    27. signed main()
    28. {
    29. ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    30. memset(f,-1,sizeof(f));
    31. cin>>q;
    32. while(q--)
    33. {
    34. cin>>b>>l>>r;
    35. ll ans=sol(r)-sol(l-1);
    36. cout<
    37. }
    38. return 0;
    39. }
    40. //

    SP10606 BALNUM - Balanced Numbers 

     和昨天做的那道是一样的,用一个二进制数来记录0-9的状态,为了记录偶数出现奇数次以及技术出现偶数次还需要在用一个数记录,所以记忆化搜索的时候也是多了一个状态,注意到这个地方就可以了

    1. #include
    2. using namespace std;
    3. #define int long long
    4. const int N = 1e5+100;
    5. const double eps=1e-8;
    6. const double pi=acos(-1);
    7. int f[12][1024][1024],num[65],t,len;
    8. int dfs(int pos,int limit,int lead,int sta,int chk)
    9. {
    10. if(pos==0)
    11. {
    12. // stackst;
    13. // int xx=chk;
    14. // while(xx) st.push(xx%2),xx/=2;
    15. // while(!st.empty()){cout<
    16. // cout<
    17. // cout<
    18. return (sta==chk);
    19. }
    20. if(!limit&&!lead&&f[pos][sta][chk]!=-1) return f[pos][sta][chk];
    21. int res=0,up=limit?num[pos]:9;
    22. for(int i=0;i<=up;i++)
    23. {
    24. res+=dfs(pos-1,i==up&&limit,lead&&(i==0),(lead&&(i==0))?0:(sta^(1<0))?0:(chk|((i&1)?0:(1<
    25. }
    26. if(!limit&&!lead) f[pos][sta][chk]=res;
    27. return res;
    28. }
    29. int sol(int x)
    30. {
    31. len=0;
    32. while(x) num[++len]=x%10,x/=10;
    33. return dfs(len,1,1,0,0);
    34. }
    35. signed main()
    36. {
    37. ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    38. cin>>t;
    39. memset(f,-1,sizeof(f));
    40. while(t--)
    41. {
    42. int a,b;
    43. cin>>a>>b;
    44. int ans=sol(b)-sol(a-1);
    45. cout<
    46. }
    47. return 0;
    48. }
    49. //

    P4124 [CQOI2016]手机号码

    这题其实不难,但是让自己的马虎和不知道为什么错的写法搞心态了,三次出现,就让a为上一位出现的数,让b位上两位出现的数,8和4分别用一个变量表示,再用一个sco变量判断是否已经合法就可以了

    1. #include
    2. using namespace std;
    3. #define int long long
    4. const int N = 1e5+100;
    5. const double eps=1e-8;
    6. const double pi=acos(-1);
    7. int f[13][10][10][2][2][2],num[13];
    8. int dfs(int pos,int a,int b,int eight,int four,int sco,int limit)
    9. {
    10. if(eight&&four) return 0;
    11. if(pos==0) return sco;
    12. if(!limit&&f[pos][a][b][eight][four][sco]!=-1) return f[pos][a][b][eight][four][sco];
    13. int up=limit?num[pos]:9,res=0;
    14. for(int i=0;i<=up;i++)
    15. {
    16. //if(i==eight&&four||i==four&&eight) return 0;
    17. res+=dfs(pos-1,i,a,eight||(i==8),four||(i==4),sco||(i==a&&i==b),limit&&(i==up));
    18. }
    19. if(!limit) f[pos][a][b][eight][four][sco]=res;
    20. return res;
    21. }
    22. int sol(int x)
    23. {
    24. int len=0;
    25. while(x) num[++len]=x%10,x/=10;
    26. if(len!=11) return 0;//还是要判断的,因为可能会有L-1的情况是10位数
    27. int res=0;
    28. for(int i=1;i<=num[len];i++)//处理第一位会有前导0的情况
    29. res+=dfs(len-1,i,-1,i==8,i==4,0,i==num[len]);
    30. return res;
    31. }
    32. signed main()
    33. {
    34. ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    35. int l,r;
    36. memset(f,-1,sizeof(f));
    37. cin>>l>>r;
    38. int ans=sol(r)-sol(l-1);
    39. cout<
    40. return 0;
    41. }
    42. //

    1073E - Segment Sum 数位dp

    需要dfs参数有limit,lead,pos,sta(各个位的状态,1表示含有这个位的数,0表示没有),然后发现f[pos][sta]会吞掉许多状态,然后就又加了个sum表示pos这一位,状态为sta,数为sum时的状态,但是这样的状态好像又过于多了,然后W,R,T,M的错误全出了一遍还是没有做出来,只能去看题解,,

    还是一开始的状态f[pos][sta],发现对于pos位,如果选的数为x,那么这一位对总答案的贡献就是x*pow[pos-1]*cnt,pow数组是10的平方,cnt是后续合法的个数,那么f[pos][sta]设为从pos开始后续所有合法状态的和,但是还是一样的问题,只有一个f数组是记录不了所有的状态的,所以还需要一个数组g,g[pos][sta]表示后续有多少合法的状态数,转移的时候

    g[pos][sta]=sum(g[pos-1][sta']),sta'表示pos-1位所有满足条件的位,

    f[pos][sta]=sum(f[pos][sta]+x*pow[pos-1]*g[pos-1][sta']+f[pos-1][sta']),

    边界条件是如果sta满足条件的话g[pos][sta]就是1,f无论怎样都是0;

    1. #include
    2. using namespace std;
    3. #define int long long
    4. const int N = 1e6+5;
    5. const int mod=998244353;
    6. const double eps=1e-8;
    7. const double pi=acos(-1);
    8. int qpow(int a,int b)
    9. {
    10. int res=1;
    11. while(b)
    12. {
    13. if(b&1) res=res*a%mod;
    14. a=a*a%mod;
    15. b>>=1;
    16. }
    17. return res;
    18. }
    19. int getinv(int a){return qpow(a,mod-2);}
    20. int k,num[22],cnt,p[20];
    21. pair<int,int>f[20][1024];
    22. pair<int,int> dfs(int pos,int limit,int lead,int sta)
    23. {
    24. if(pos==0)
    25. {
    26. return {__builtin_popcount(sta)<=k,0};
    27. }
    28. if(!limit&&!lead&&f[pos][sta].first!=-1) return f[pos][sta];
    29. int up=limit?num[pos]:9;
    30. pair<int,int> res(0,0);
    31. for(int i=0;i<=up;i++)
    32. {
    33. pair<int,int>tmp=dfs(pos-1,i==up&&limit,!i&&lead,!i&&lead?0:sta|(1<
    34. res=pair<int,int>((res.first+tmp.first)%mod,(((tmp.first*i%mod)*p[pos-1]%mod+tmp.second)%mod+res.second)%mod);
    35. }
    36. if(!limit&&!lead) f[pos][sta]=res;
    37. return res;
    38. }
    39. int sol(int x)
    40. {
    41. int len=0;
    42. while(x) num[++len]=x%10,x/=10;
    43. for(int i=0;i<=18;i++)
    44. for(int j=0;j<=1023;j++) f[i][j].first=f[i][j].second=-1;
    45. return dfs(len,1,1,0).second;
    46. }
    47. signed main()
    48. {
    49. ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    50. int l,r;
    51. p[0]=1;
    52. for(int i=1;i<=18;i++) p[i]=p[i-1]*10%mod;
    53. cin>>l>>r>>k;
    54. int ans=((sol(r)-sol(l-1))%mod+mod)%mod;
    55. cout<
    56. return 0;
    57. }
    58. //

    Beautiful numbers 

     1~9的最小公倍数是2520,如果一个数sum取余多个数的最小公倍数是0,那么他取余这些数也一定是0,所以记录状态时只需要记录最小公倍数就可以了,更进一步发现2520只有40多个因子,而对题目有用的也只是这些因子而已,所以只记录这些因子就可以;sum%2520和sum在题目中的意义是一样的,所以sum这一维可以缩到2520,所以总的状态就是dp[pos][sum][book[lcm]],pos是位置,最大19,sum是取余后的sum,最大2520,book[lcm]是因子的编号最大48,所以不会爆内存

    题解 CF55D 【Beautiful numbers】 - _Kurumi_ 的博客 - 洛谷博客

    1. #include
    2. using namespace std;
    3. #define endl '\n'
    4. #define int long long
    5. const int N = 2e5+5;
    6. const int mod=2520;
    7. const int inf=1e18;
    8. const double eps=1e-8;
    9. const double pi=acos(-1);
    10. int qpow(int a,int b)
    11. {
    12. int res=1;
    13. while(b)
    14. {
    15. if(b&1) res=res*a%mod;
    16. a=a*a%mod;
    17. b>>=1;
    18. }
    19. return res;
    20. }
    21. int getinv(int a){return qpow(a,mod-2);}
    22. int Lcm(int a,int b){return a*b/__gcd(a,b);}
    23. int t,num[22],len,f[22][2555][55],book[2525];
    24. void init()
    25. {
    26. memset(f,-1,sizeof(f));
    27. int x=0;
    28. for(int i=1;i<=mod;i++)
    29. if(mod%i==0) book[i]=++x;
    30. }
    31. int dfs(int pos,int limit,int lcm,int sum)
    32. {
    33. if(pos==0)
    34. {
    35. //cout<
    36. return sum%lcm==0;
    37. }
    38. if(!limit&&f[pos][sum][book[lcm]]!=-1) return f[pos][sum][book[lcm]];
    39. int up=limit?num[pos]:9,res=0;
    40. for(int i=0;i<=up;i++)
    41. {
    42. int nsum=(sum*10+i)%mod;
    43. int nlcm=lcm;
    44. if(i) nlcm=Lcm(nlcm,i);
    45. res+=dfs(pos-1,i==up&&limit,nlcm,nsum);
    46. }
    47. if(!limit) f[pos][sum][book[lcm]]=res;
    48. return res;
    49. }
    50. int sol(int x)
    51. {
    52. len=0;
    53. while(x) num[++len]=x%10,x/=10;
    54. return dfs(len,1,1,0);
    55. }
    56. signed main()
    57. {
    58. ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    59. //freopen("in.txt","r",stdin);
    60. cin>>t;
    61. init();
    62. while(t--)
    63. {
    64. int l,r;
    65. cin>>l>>r;
    66. //cout<
    67. int ans=sol(r)-sol(l-1);
    68. cout<
    69. }
    70. return 0;
    71. }
    72. //

    P4127 [AHOI2009]同类分布 

    参数有pos,limit,sum(各个数位的和),ori(原数),然后f[pos][sum][ori],但是由于ori太大所以要进行取模处理,但是模数是在变化的,因为sum一直是变化的,所以直接枚举模数,最后的判断条件就是ori==0&&sum==mod?1:0,然后这题就可以做了

    P4127 同类分布【数位dp】 - Mathison 的博客 - 洛谷博客

    1. #include
    2. using namespace std;
    3. #define endl '\n'
    4. #define int long long
    5. const int N = 2e5+5;
    6. int mod=998244353;
    7. const int inf=1e18;
    8. const double eps=1e-8;
    9. const double pi=acos(-1);
    10. int qpow(int a,int b)
    11. {
    12. int res=1;
    13. while(b)
    14. {
    15. if(b&1) res=res*a%mod;
    16. a=a*a%mod;
    17. b>>=1;
    18. }
    19. return res;
    20. }
    21. int getinv(int a){return qpow(a,mod-2);}
    22. int Lcm(int a,int b){return a*b/__gcd(a,b);}
    23. int num[20],f[20][200][200];
    24. int dfs(int pos,int limit,int sum,int ori)
    25. {
    26. if(pos==0)
    27. {
    28. if(sum==0) return 0;
    29. return ori==0&&sum==mod?1:0;
    30. }
    31. if(!limit&&f[pos][sum][ori]!=-1) return f[pos][sum][ori];
    32. int up=limit?num[pos]:9,res=0;
    33. for(int i=0;i<=up;i++)
    34. {
    35. res+=dfs(pos-1,i==up&&limit,sum+i,(ori*10+i)%mod);
    36. }
    37. if(!limit) f[pos][sum][ori]=res;
    38. return res;
    39. }
    40. int sol(int x)
    41. {
    42. int len=0;
    43. while(x) num[++len]=x%10,x/=10;
    44. int res=0;
    45. for(mod=1;mod<=9*len;mod++)
    46. {
    47. memset(f,-1,sizeof(f));
    48. res+=dfs(len,1,0,0);
    49. }
    50. return res;
    51. }
    52. signed main()
    53. {
    54. ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    55. //freopen("in.txt","r",stdin);
    56. int a,b;
    57. cin>>a>>b;
    58. //cout<
    59. int ans=sol(b)-sol(a-1);
    60. cout<
    61. return 0;
    62. }
    63. //

    2022广州 M - XOR Sum 数位dp

    给定n,m,k(0<=n<=1e15,0<=m<=1e12,1<=k<=18),

    求长度为k的数组a,ai为[0,m]的整数,满足\sum_{i=1}^{k}\sum_{j=1}^{i-1}a_{i}\bigoplus a_{j}=n的方案数

    这个公式其实就是数组中的数两两异或,那么对于某一个二进制位bit来说,这位上为1的数有x个,那么这一位的贡献就是x*(k-x)*(1<

    考虑数位dp,dp[x][r][cur]表示现在是m的从高到低第x个二进制位,余数有r个(当前这位需要r个1才可以抵消),当前有cur个数到达了上界可以产生的方案数,分两种情况,如果a[x]==0,那么那些取到上界的数就只能取0,没有取到上界的可以取1,枚举去了多少1,可以消去的数就是i*(k-i);

    如果a[x]==1,那么那些取到上界的或没取到上界的都可以取0或1,二重枚举,可以消去的数就是(i+j)*(k-i-j);

    2022 China Collegiate Programming Contest (CCPC) Guangzhou Onsite M. XOR Sum(数位dp 数位背包)_Code92007的博客-CSDN博客

    1. #include
    2. #define int long long
    3. #define ll __int128
    4. #define lowbit(x) ((x)&(-x))
    5. #define endl '\n'
    6. #define pause system("pause")
    7. using namespace std;
    8. const int N=1e6+5;
    9. const int mod=1e9+7;
    10. const int inf=1e18;
    11. int qpow(int a,int b)
    12. {
    13. int res=1;
    14. while(b)
    15. {
    16. if(b&1) res=res*a%mod;
    17. a=a*a%mod;
    18. b>>=1;
    19. }
    20. return res;
    21. }
    22. int getinv(int a){return qpow(a,mod-2);}
    23. int dp[64][258][20],a[64],c[66][66],n,m,k,M=200;
    24. int dfs(int x,int r,int cur)
    25. {
    26. if(x<0) return r==0;
    27. if(r>M) return 0;
    28. if(dp[x][r][cur]!=-1) return dp[x][r][cur];
    29. int ans=0;
    30. int nb=(x==0?0:(n>>(x-1)&1));
    31. if(a[x]==0)
    32. {
    33. for(int i=0;i<=k-cur;i++)
    34. {
    35. int nr=r-i*(k-i);
    36. if(nr<0) continue;
    37. ans=(ans+dfs(x-1,(nr<<1)|nb,cur)*c[k-cur][i]%mod)%mod;
    38. }
    39. }
    40. else
    41. {
    42. for(int i=0;i<=cur;i++)
    43. {
    44. for(int j=0;j<=k-cur;j++)
    45. {
    46. int nr=r-(i+j)*(k-i-j);
    47. if(nr<0) continue;
    48. ans=(ans+(dfs(x-1,(nr<<1)|nb,i)*c[cur][i]%mod)*c[k-cur][j]%mod)%mod;
    49. }
    50. }
    51. }
    52. return dp[x][r][cur]=ans;
    53. }
    54. int cal()
    55. {
    56. if(k==1)return !n;
    57. if(!m)return !n;
    58. memset(dp,-1,sizeof(dp));
    59. int len=0,x=m;
    60. while(x)
    61. {
    62. a[len++]=x%2;
    63. x/=2;
    64. }
    65. int rn=0;
    66. for(int i=len;i<=50;i++)
    67. {
    68. if(n>>i&1)
    69. {
    70. rn+=1<<(i-len);
    71. if(rn>=M) return 0;
    72. }
    73. }
    74. return dfs(len,rn,k);
    75. }
    76. signed main()
    77. {
    78. ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    79. c[0][0]=1;
    80. for(int i=1;i<=64;i++)
    81. {
    82. c[i][0]=c[i][i]=1;
    83. for(int j=1;j
    84. c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;
    85. }
    86. cin>>n>>m>>k;
    87. int ans=cal();
    88. cout<
    89. return 0;
    90. }

  • 相关阅读:
    身份认证与提权攻击中的专属名词与缩略语整理
    LTE小区搜索过程及SCH/BCH设计
    300分钟吃透分布式缓存-15讲:如何深入理解、应用及扩展 Twemproxy?
    Ceph入门到精通-sysctl参数优化
    是时候回答【我为什么要学习 Go 语言(golang)】这个问题了
    Day 06 python学习笔记
    对Transformer中Add&Norm层的理解
    Docker Desktop安装以及MYSQL, GRAFANA安装
    基础生态学名词解释
    Mac 电脑查看本地maven,及私有仓库的搭建与使用【nexus的配置与使用】
  • 原文地址:https://blog.csdn.net/weixin_52621204/article/details/127292042