码迷,mamicode.com
首页 > 其他好文 > 详细

数位dp

时间:2015-07-19 18:14:11      阅读:221      评论:0      收藏:0      [点我收藏+]

标签:acm   summary   

 2015-07-19 16:02 字数 6806 阅读 0

KNOW

数位DP

1. 自己

1.1 总结

  • 明确dp中pos所指代的具体位置,基本知识点不能糊涂
  • 清楚递推关系 思路要整理清楚

1.2 Question

这道题完完全全是自己做出来的,本以为是数学题,但一看好像数位DP也能做,于是开始脑补,坚持尝试了一个小时终于给攻了出来,很开心很开心哈哈

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. #define maxn 40
  4. int L, R;
  5. int d[maxn][maxn][maxn];
  6. int digit[maxn];
  7. int dfs(int pos, int num1, int num2, int have, int flag)/// num1 & num2 means before the "pos" the amount of "0" & "1".
  8. {
  9. if(pos == -1)
  10. return num1 >= num2;/// num1 represent the number of 0, and num2 means 1
  11. if(!flag && d[pos][num1][num2]!=-1)
  12. return d[pos][num1][num2];
  13. int ans = 0;
  14. int end = flag ? digit[pos] : 1;
  15. for(int i=0; i<=end; i++) ///know this value is on "pos" position.
  16. {
  17. int t1 = num1, t2 = num2, nhave = have;
  18. if(have && i==0) t1++;
  19. if(i==1)
  20. {
  21. nhave = 1;
  22. t2++;
  23. }
  24. ans += dfs(pos-1, t1, t2, nhave, flag&&i==end);
  25. }
  26. if(!flag)
  27. d[pos][num1][num2] = ans;
  28. return ans;
  29. }
  30. int cal(int x)
  31. {
  32. int pos = 0;
  33. if(x == -1)
  34. return 0;
  35. while(x)
  36. {
  37. digit[pos++] = x % 2;
  38. x /= 2;
  39. }
  40. return dfs(pos-1, 0, 0, 0, 1);
  41. }
  42. int main()
  43. {
  44. memset(d, -1, sizeof(d));
  45. while(~scanf("%d%d", &L, &R))
  46. {
  47. printf("%d\n", cal(R) - cal(L-1));
  48. }
  49. return 0;
  50. }

2. 大神

Go For It 
Just Open It 
hdu 2089

  1. 数位dp
  2. /*
  3. 求[1,n]内有多少个数字,该数字有13,且能被13整除 n<=10^9
  4. x % 13 = 0
  5. (pre*10^pos + next) % 13 = 0 pre是之前确定的部分
  6. 需要的参数为pre , pos , 状态have
  7. have记录pre拥有"13",pos+1位为"1",没有"13" 分别用have = 2,1,0表示
  8. 然后记忆化搜索
  9. */
  10. #include<cstdio>
  11. #include<cstring>
  12. #include<algorithm>
  13. using namespace std;
  14. int dp[10][13][3];
  15. int digit[10];
  16. __int64 dfs(int pos , int pre , int have , bool doing)
  17. {
  18. if(pos == -1)
  19. return have == 2 && pre == 0;
  20. if(!doing && dp[pos][pre][have] != -1)
  21. return dp[pos][pre][have];
  22. int ans = 0;
  23. int end = doing ? digit[pos] : 9;
  24. for(int i = 0 ; i <= end ; i ++)
  25. {
  26. int npre = (pre*10 + i) % 13;
  27. int nhave = have;
  28. if(have == 0 && i == 1)
  29. nhave = 1;
  30. else if(have == 1 && i != 1)
  31. nhave = 0;
  32. if(have == 1 && i == 3)
  33. nhave = 2;
  34. ans += dfs(pos-1 , npre , nhave , doing && i == end );
  35. }
  36. if(!doing)
  37. dp[pos][pre][have] = ans;
  38. return ans;
  39. }
  40. int cal(int x)
  41. {
  42. int pos = 0;
  43. while(x)
  44. {
  45. digit[pos++] = x % 10;
  46. x /= 10;
  47. }
  48. return dfs(pos - 1 , 0 , 0 , 1);
  49. }
  50. int main()
  51. {
  52. memset(dp,-1,sizeof(dp));
  53. int n;
  54. while(~scanf("%d",&n))
  55. printf("%d\n",cal(n));
  56. return 0;
  57. }
  58. /*
  59. 问[1,n]内有多少个数字包含"49"
  60. 这里用记忆化搜索
  61. 更加好写,而且快
  62. hdu 3652 是加强版
  63. */
  64. #include<cstdio>
  65. #include<algorithm>
  66. using namespace std;
  67. //有点明白了 这里的dp[i,j]是指当 n ~ //i的前缀已经确定的情况之下,之后的数随意,能够有多少满足条件的数
  68. //今天是一个新的起点 我要从数位dp这里来建立自己的自信心
  69. //加油 最棒的自己
  70. __int64 dp[20][5];
  71. int digit[20];
  72. __int64 dfs(int pos , int have , bool doing)
  73. {
  74. if(pos == -1)
  75. return have == 2;
  76. if(!doing && dp[pos][have] !=-1)
  77. return dp[pos][have];
  78. __int64 ans = 0;
  79. int end = doing ? digit[pos] : 9;
  80. for(int i = 0 ; i <= end; i++)
  81. {
  82. int nhave = have;
  83. if(have == 1 && i != 4)
  84. nhave = 0;
  85. if(have == 0 && i == 4)
  86. nhave = 1;
  87. if(have == 1 && i == 9)
  88. nhave = 2;
  89. ans += dfs(pos-1 , nhave , doing && i == end);
  90. }
  91. if(!doing)
  92. {
  93. dp[pos][have] = ans;
  94. }
  95. return ans;
  96. }
  97. __int64 cal(__int64 x)
  98. {
  99. int pos = 0;
  100. while(x)
  101. {
  102. digit[pos++] = x % 10;
  103. x /= 10;
  104. }
  105. return dfs(pos - 1 , 0 , 1);
  106. }
  107. int main()
  108. {
  109. memset(dp,-1,sizeof(dp));
  110. int T;
  111. for(scanf("%d",&T) ; T-- ;)
  112. {
  113. __int64 n;
  114. scanf("%I64d",&n);
  115. printf("%I64d\n",cal(n));
  116. }
  117. return 0;
  118. }
  119. 用这种方法写,一个流程是,列出式子(pre*10^pos + next) pre是确定的,next是变量
  120. 所以参数就为pre,pos加上一些其他的,还有一个标记doing,表示是计算有上界限制的还是没有上界限制(所有情况,即end=9
  121. dfs(pos-1 , npre , doing && i ==end)
  122. /**//*
  123. 题目描述见另外一份
  124. 平衡,即∑a[i]*(i-o) = 0 o为支点
  125. 对于一个数,支点o是唯一的,所以不会有重复计数的情况(但有一种特殊的,就是0,00,000等都是一样的,会计算多次,
  126. 最后减去即可)
  127. 假设检查到pos处,对于上面的式子∑a[i]*(i-o) = 0,这里确定了支点为o
  128. 之前的数其∑a[i]*(i-o)的结果为pre
  129. 所以参数需要为pos , o , pre
  130. 当检查到pos=-1时,return pre == 0
  131. 否则,看当前是计算所有情况还是具体情况(doing)
  132. 如果是所有情况且dp值!=-1,直接return
  133. 否则就枚举0到end
  134. 而支点o需要在最外层枚举出来
  135. */
  136. #include<cstdio>
  137. #include<cstring>
  138. #include<algorithm>
  139. using namespace std;
  140. long long dp[19][19][2000];
  141. int digit[19];
  142. void init()
  143. {
  144. memset(dp,-1,sizeof(dp));
  145. }
  146. long long dfs(int pos , int o , int pre , bool doing)
  147. {
  148. if(pos == -1)
  149. return pre == 0;
  150. if(!doing && dp[pos][o][pre] != -1)
  151. return dp[pos][o][pre];
  152. long long ans = 0;
  153. int end = doing ? digit[pos] : 9;
  154. for(int i = 0 ; i <= end ; i ++)
  155. {
  156. int npre = pre;
  157. npre += (pos-o)*i;
  158. ans += dfs(pos-1 , o , npre , doing && i == end);
  159. }
  160. if(!doing)
  161. dp[pos][o][pre] = ans;
  162. return ans;
  163. }
  164. long long cal(long long x)
  165. {
  166. int pos = 0;
  167. while(x)
  168. {
  169. digit[pos++] = x % 10;
  170. x /= 10;
  171. }
  172. long long ans = 0;
  173. for(int o = 0 ; o < pos ; o ++)
  174. {
  175. ans += dfs(pos-1 , o , 0 , 1);
  176. }
  177. return ans - (pos-1);//duplicate 0
  178. }
  179. int main()
  180. {
  181. init();
  182. int T;
  183. for(scanf("%d",&T) ; T--; )
  184. {
  185. long long left , right;
  186. scanf("%lld%lld",&left , &right);
  187. printf("%lld\n",cal(right) - cal(left - 1));
  188. }
  189. return 0;
  190. }
  191. /* ***********************************************
  192. Author :kuangbin
  193. Created Time :2013/9/14 星期六 12:45:42
  194. File Name :2013成都网络赛\1007.cpp
  195. ************************************************ */
  196. #pragma comment(linker, "/STACK:1024000000,1024000000")
  197. #include <stdio.h>
  198. #include <string.h>
  199. #include <iostream>
  200. #include <algorithm>
  201. #include <vector>
  202. #include <queue>
  203. #include <set>
  204. #include <map>
  205. #include <string>
  206. #include <math.h>
  207. #include <stdlib.h>
  208. #include <time.h>
  209. using namespace std;
  210. int dp[20][200000];
  211. int bit[20];
  212. int dfs(int pos,int num,bool flag)
  213. {
  214. if(pos == -1)return num >= 0;
  215. if(num < 0)return 0;
  216. if(!flag && dp[pos][num] != -1)
  217. return dp[pos][num];
  218. int ans = 0;
  219. int end = flag?bit[pos]:9;
  220. for(int i = 0;i <= end;i++)
  221. {
  222. ans += dfs(pos-1,num - i*(1<<pos),flag && i==end);
  223. }
  224. if(!flag)dp[pos][num] = ans;
  225. return ans;
  226. }
  227. int F(int x)
  228. {
  229. int ret = 0;
  230. int len = 0;
  231. while(x)
  232. {
  233. ret += (x%10)*(1<<len);
  234. len++;
  235. x /= 10;
  236. }
  237. return ret;
  238. }
  239. int A,B;
  240. int calc()
  241. {
  242. int len = 0;
  243. while(B)
  244. {
  245. bit[len++] = B%10;
  246. B/=10;
  247. //cout<<bit[len-1]<<endl;
  248. }
  249. //cout<<F(A)<<endl;
  250. return dfs(len-1,F(A),1);
  251. }
  252. int main()
  253. {
  254. //freopen("in.txt","r",stdin);
  255. //freopen("out.txt","w",stdout);
  256. int T;
  257. int iCase = 0;
  258. scanf("%d",&T);
  259. memset(dp,-1,sizeof(dp));
  260. while(T--)
  261. {
  262. iCase++;
  263. scanf("%d%d",&A,&B);
  264. printf("Case #%d: %d\n",iCase,calc());
  265. }
  266. return 0;
  267. }
  268. /*
  269. * HDU 2089
  270. * 求一个区间内,不出现4和连续的62的数的个数。
  271. * 这题可以暴力打表。
  272. * 数位DP也可以做。
  273. *
  274. */
  275. #include <iostream>
  276. #include <stdio.h>
  277. #include <algorithm>
  278. #include <string.h>
  279. using namespace std;
  280. int dp[10][6];
  281. /*
  282. * dp[i][0],表示长度为i,不存在不吉利数字
  283. * dp[i][7],表示长度为i,不存在不吉利数字,且最高位为2
  284. * dp[i][8],表示长度为i,存在不吉利数字
  285. */
  286. void init()
  287. {
  288. dp[0][0]=1;dp[0][9]=0;dp[0][10]=0;
  289. for(int i=1;i<=6;i++)
  290. {
  291. dp[i][0]=dp[i-1][0]*9-dp[i-1][11];//在最高位加上除4以外的9个数字,但要减掉2之前加上6
  292. dp[i][12]=dp[i-1][0];//在不含不吉利数字的最高位加上2
  293. dp[i][13]=dp[i-1][14]*10+dp[i-1][0]+dp[i-1][15];
  294. //在已有不吉利数字前加任意数字,或者无不吉利数字的最高位加4,或者在2前面加6
  295. }
  296. }
  297. int bit[10];
  298. int solve(int n)
  299. {
  300. int len=0;
  301. int tmp=n;
  302. while(n)
  303. {
  304. bit[++len]=n%10;
  305. n/=10;
  306. }
  307. bit[len+1]=0;
  308. int ans=0;
  309. bool flag=false;
  310. for(int i=len;i>=1;i--)
  311. {
  312. ans+=dp[i-1][16]*bit[i];
  313. if(flag)//高位已经出现4或者62,后面随意
  314. ans+=dp[i-1][0]*bit[i];
  315. if(!flag&&bit[i]>4)
  316. ans+=dp[i-1][0];
  317. if(!flag&&bit[i+1]==6&&bit[i]>2)
  318. ans+=dp[i][17];
  319. if(!flag&&bit[i]>6)
  320. ans+=dp[i-1][18];
  321. if(bit[i]==4||(bit[i+1]==6&&bit[i]==2))
  322. flag=true;
  323. }
  324. if(flag)ans++;//这个数本身
  325. return tmp-ans;
  326. }
  327. int main()
  328. {
  329. //freopen("in.txt","r",stdin);
  330. //freopen("out.txt","w",stdout);
  331. init();
  332. int n,m;
  333. while(scanf("%d%d",&n,&m)==2)
  334. {
  335. if(n==0 && m==0)break;
  336. printf("%d\n",solve(m)-solve(n-1));
  337. }
  338. return 0;
  339. }
  340. F(x):
  341. #include<cstdio>
  342. #include<cstring>
  343. #define maxn 16
  344. int dp[maxn][111111];
  345. int d[maxn];
  346. int n;
  347. long long tt;
  348. long long dfs(int len ,int pre ,bool fp)
  349. {
  350. if(pre<0)return 0;//说明上一层枚举的数超过了上限,没有可用的情况
  351. if(!len)return 1;//说明上一层是个位.那么只需要把各个数累加起来就可以
  352. if(!fp&&dp[len][pre]!=-1)return dp[len][pre];//记忆化搜索
  353. int fpmax=fp?d[len]:9;//取该位取值的最大值
  354. int ret=0;
  355. for(int i=0;i<=fpmax;i++){//从最大长度向下,每一个长度的所有取值都要遍历到,
  356. //一旦该位的取值不是紧贴最大值,fp就false.
  357. ret+= dfs(len-1,pre-i*(1<<(len-1)),fp&&i==fpmax);
  358. }
  359. if(!fp)dp[len][pre]=ret;//记录结果
  360. return ret;
  361. }
  362. long long calc(long long a)
  363. {
  364. int len=0;
  365. memset(d,0,sizeof(d));
  366. while(a){
  367. d[++len]=a%10;
  368. a/=10;
  369. }
  370. return dfs(len,tt,true);
  371. }
  372. int get(int x)
  373. {
  374. int tmp=1;
  375. int ans=0;
  376. while(x){
  377. ans+=(x%10)*tmp;
  378. x/=10;
  379. tmp<<=1;
  380. }
  381. return ans;
  382. }
  383. int main()
  384. {
  385. long long a,b;
  386. int nc;
  387. scanf("%d",&nc);
  388. int d=1;
  389. memset(dp,-1,sizeof(dp));
  390. while(nc--){
  391. scanf("%I64d%I64d",&a,&b);
  392. tt=get(a);
  393. printf("Case #%d: %I64d\n",d++,calc(b));
  394. }
  395. return 0;
  396. }

版权声明:本文为博主原创文章,未经博主允许不得转载。

数位dp

标签:acm   summary   

原文地址:http://blog.csdn.net/dojintian/article/details/46955533

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!