题解-Roman and Numbers

前置知识:

数位 \(\texttt{dp}\) </>


\(\color{#9933cc}{\texttt{Roman and Numbers}}\)

给定 \(n\) 和 \(m\),求将 \(n\) 的各位数字重新排列(不允许有前导 \(0\)),求可以构造几个能被 \(m\) 整除。

数据范围:\(1\le n\le 10^{18}\),\(1\le m\le 100\)。


用数位 \(\texttt{dp}\) 代码又短时间又优又好理解,为什么没人玩呢?


把 \(n\) 的各位数字拿出来排序一下,然后把每个数有没有用过状压。

for(;n;n/=10) d.pb(n%10);
sort(d.begin(),d.end()),len=d.size();

选数字的时候只允许用相同数字中第一个没用过的。


\(\texttt{Dfs}\) 中:

  1. \(w\):要找从右往左第几位。
  2. \(st\):当前数字使用状态。
  3. \(sum\):左 \(len-w\) 位数字形成的数 \(\bmod m\) 的余数。
il lng Dfs(re int w,re int st,re int sum){
if(!w) return sum==0;//判断被 m 整除
if(~f[st][sum]) return f[st][sum];
re lng res=0;
for(re int i=0;i<len;i++)
if(!((1<<i)&st)&&(i==0||d[i]!=d[i-1]||((1<<(i-1))&st))) //*
res+=Dfs(w-1,st|(1<<i),(sum*10+d[i])%m);
return f[st][sum]=res; //记忆化,记录答案
}

其中 \(*\) 处的判断:

  1. 该数字未用过。
  2. 该数字前的相同数字都用过。

以保证使用顺序,防止重复统计。

关于 \(f\) 记忆化数组:

现在是 \(f_{st,sum}\),本来应该是 \(f_{w,st,sum}\)。这里就讲讲 \(f_{w,st,sum}\) 记忆化的缺点:

  1. \(1\le w\le 18\),\(1\le st\le 2^{18}\),\(1\le sum<m\le 100\),必然 \(\color{#117}{\texttt{MLE}}\)。
  2. \(st\) 中 \(1\) 的数量 \(cnt\) 必然满足 \(cnt=len-w\),所以只记录 \(st\) 不会重合答案。

时间复杂度 \(\Theta(2^{len}m)\)。


Code

#include <bits/stdc++.h>
using namespace std; //&Start
#define re register
#define il inline
#define mk make_pair
#define pb push_back
#define db double
#define lng long long
#define fi first
#define se second
#define inf 0x3f3f3f3f //&Data
const int W=18,M=100;
int m,len;
lng n,f[1<<W|7][M|7];
vector<int> d; //&Digitdp
il void Pre(){memset(f,-1,sizeof f);}
il lng Dfs(re int w,re int st,re int sum){
if(!w) return sum==0;
if(~f[st][sum]) return f[st][sum];
re lng res=0;
for(re int i=0;i<len;i++)
if(!((1<<i)&st)&&(i==0||d[i]!=d[i-1]||((1<<(i-1))&st)))
res+=Dfs(w-1,st|(1<<i),(sum*10+d[i])%m);
return f[st][sum]=res;
}
il lng DP(){
for(;n;n/=10) d.pb(n%10);
sort(d.begin(),d.end()),len=d.size();
re lng res=0;
for(re int i=0;i<len;i++)
if(d[i]&&(i==0||d[i]!=d[i-1])) // 这里也要判断!这是最容易错的地方
res+=Dfs(len-1,1<<i,d[i]%m);
return res;
} //&Main
int main(){
scanf("%lld%d",&n,&m),Pre();
printf("%lld\n",DP());
return 0;
}

祝大家学习愉快!

最新文章

  1. Linux学习之路&mdash;Linux文件权限
  2. HDU 1693 Eat the Trees(插头DP)
  3. Asp.net WebApi + EF 单元测试架构 DbContext一站到底
  4. maven异常解决:编码GBK的不可映射字符
  5. ASP.NET 5 :上传文件(转)
  6. 串行通讯之.NET SerialPort
  7. 在windows下使用linux的开发环境
  8. [tarjan] hdu 3836 Equivalent Sets
  9. Elasticsearch Document
  10. Beamer制作索引
  11. Python3.0科学计算学习之绘图(四)
  12. 关于canvas补充说明
  13. 使用Jyhon脚本和PMI模块监控WAS性能数据
  14. node.js 安装 测试
  15. Kernel 4.9的BBR拥塞控制算法与锐速
  16. POJ 3076 SUKODU [Dangcing Links DLX精准覆盖]
  17. App.config和Web.config配置文件的配置节点的解析
  18. 用@resource注解方式完成属性装配
  19. Python虚拟环境包导出
  20. 在 linux 下使用 CMake 构建应用程序

热门文章

  1. 适合 Go 新手学习的开源项目——在 GitHub 学编程
  2. 利用虚拟化环境虚拟nvme盘
  3. Python基础数据类型与for循环
  4. 两种不同的扩展Scrum的方式
  5. python编码规范以及推导式的编写
  6. Spring Cloud实战 | 第九篇:Spring Cloud整合Spring Security OAuth2认证服务器统一认证自定义异常处理
  7. 还不懂java类加载机制的,建议看下这份阿里技术官总结的笔记!
  8. Vim常用按键
  9. C语言讲义——传值、传引用
  10. SpringCloud 源码系列(2)—— 注册中心 Eureka(中)