您的位置:首页 > 其它

数位dp

2015-07-19 16:04 302 查看
@JintianGo
2015-07-19
 16:02
字数 6806
阅读 0


KNOW


数位DP

1. 自己

1.1 总结
1.2 Question

2. 大神


数位DP


1. 自己


1.1 总结

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


1.2 Question

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

[code]#include <bits/stdc++.h>


using namespace std;

#define maxn 40


int L, R;

int d[maxn][maxn][maxn];

int digit[maxn];


int dfs(int pos, int num1, int num2, int have, int flag)/// num1 & num2 means before the "pos" the amount of "0" & "1".

{

if(pos == -1)

return num1 >= num2;/// num1 represent the number of 0, and num2 means 1

if(!flag && d[pos][num1][num2]!=-1)

return d[pos][num1][num2];

int ans = 0;

int end = flag ? digit[pos] : 1;

for(int i=0; i<=end; i++) ///know this value is on "pos" position.

{

int t1 = num1, t2 = num2, nhave = have;

if(have && i==0) t1++;

if(i==1)

{

nhave = 1;

t2++;

}

ans += dfs(pos-1, t1, t2, nhave, flag&&i==end);

}

if(!flag)

d[pos][num1][num2] = ans;

return ans;

}


int cal(int x)

{

int pos = 0;

if(x == -1)

return 0;

while(x)

{

digit[pos++] = x % 2;

x /= 2;

}

return dfs(pos-1, 0, 0, 0, 1);

}


int main()

{

memset(d, -1, sizeof(d));

while(~scanf("%d%d", &L, &R))

{

printf("%d\n", cal(R) - cal(L-1));

}

return 0;

}

[/code]


2. 大神

Go
For It

Just Open It

hdu 2089

[code]                     数位dp


/*

求[1,n]内有多少个数字,该数字有13,且能被13整除   n<=10^9


x % 13 = 0

(pre*10^pos + next) % 13 = 0   pre是之前确定的部分

需要的参数为pre , pos , 状态have

have记录pre拥有"13",pos+1位为"1",没有"13"   分别用have = 2,1,0表示


然后记忆化搜索


*/

#include<cstdio>

#include<cstring>

#include<algorithm>


using namespace std;


int dp[10][13][3];

int digit[10];


__int64 dfs(int pos , int pre , int have , bool doing)

{

if(pos == -1)

return have == 2 && pre == 0;


if(!doing && dp[pos][pre][have] != -1)

return dp[pos][pre][have];


int ans = 0;

int end = doing ? digit[pos] : 9;

for(int i = 0 ; i <= end ; i ++)

{

int npre = (pre*10 + i) % 13;

int nhave = have;

if(have == 0 && i == 1)

nhave = 1;

else if(have == 1 && i != 1)

nhave = 0;

if(have == 1 && i == 3)

nhave = 2;

ans += dfs(pos-1 , npre , nhave , doing && i == end );

}


if(!doing)

dp[pos][pre][have] = ans;

return ans;

}



int cal(int x)

{

int pos = 0;

while(x)

{

digit[pos++] = x % 10;

x /= 10;

}

return dfs(pos - 1 , 0 , 0 , 1);

}


int main()

{

memset(dp,-1,sizeof(dp));

int n;

while(~scanf("%d",&n))

printf("%d\n",cal(n));

return 0;

}





/*

问[1,n]内有多少个数字包含"49"

这里用记忆化搜索

更加好写,而且快


hdu 3652 是加强版

*/

#include<cstdio>

#include<algorithm>


using namespace std;


//有点明白了  这里的dp[i,j]是指当 n ~ //i的前缀已经确定的情况之下,之后的数随意,能够有多少满足条件的数


//今天是一个新的起点 我要从数位dp这里来建立自己的自信心

//加油 最棒的自己


__int64 dp[20][5];

int digit[20];


__int64 dfs(int pos , int have , bool doing)

{

if(pos == -1)

return have == 2;


if(!doing && dp[pos][have] !=-1)

return dp[pos][have];


__int64 ans = 0;

int end = doing ? digit[pos] : 9;

for(int i = 0 ; i <= end; i++)

{

int nhave = have;        

if(have == 1 && i != 4)

nhave = 0;

if(have == 0 && i == 4)

nhave = 1;

if(have == 1 && i == 9)

nhave = 2;

ans += dfs(pos-1 , nhave , doing && i == end);

}


if(!doing)

{

dp[pos][have] = ans;

}


return ans;

}


__int64 cal(__int64 x)

{

int pos = 0;

while(x)

{

digit[pos++] = x % 10;

x /= 10;

}

return dfs(pos - 1  , 0 , 1);

}


int main()

{

memset(dp,-1,sizeof(dp));

int T;

for(scanf("%d",&T) ; T-- ;)

{

__int64 n;

scanf("%I64d",&n);

printf("%I64d\n",cal(n));

}

return 0;

}



用这种方法写,一个流程是,列出式子(pre*10^pos + next)  pre是确定的,next是变量

所以参数就为pre,pos加上一些其他的,还有一个标记doing,表示是计算有上界限制的还是没有上界限制(所有情况,即end=9)

dfs(pos-1 , npre , doing && i ==end)


/**//*

题目描述见另外一份

平衡,即∑a[i]*(i-o) = 0   o为支点

对于一个数,支点o是唯一的,所以不会有重复计数的情况(但有一种特殊的,就是0,00,000等都是一样的,会计算多次,

最后减去即可)

假设检查到pos处,对于上面的式子∑a[i]*(i-o) = 0,这里确定了支点为o

之前的数其∑a[i]*(i-o)的结果为pre

所以参数需要为pos , o , pre


当检查到pos=-1时,return pre == 0

否则,看当前是计算所有情况还是具体情况(doing)

如果是所有情况且dp值!=-1,直接return

否则就枚举0到end


而支点o需要在最外层枚举出来

*/

#include<cstdio>

#include<cstring>

#include<algorithm>


using namespace std;


long long dp[19][19][2000];

int digit[19];


void init()

{

memset(dp,-1,sizeof(dp));

}


long long dfs(int pos , int o , int pre , bool doing)

{

if(pos == -1)

return pre == 0;


if(!doing && dp[pos][o][pre] != -1)

return dp[pos][o][pre];


long long ans = 0;

int end = doing ? digit[pos] : 9;

for(int i = 0 ; i <= end ; i ++)

{

int npre = pre;

npre += (pos-o)*i;

ans += dfs(pos-1 , o , npre , doing && i == end);

}


if(!doing)

dp[pos][o][pre] = ans;

return ans;

}


long long cal(long long x)

{

int pos = 0;

while(x)

{

digit[pos++] = x % 10;

x /= 10;         

}

long long ans = 0;

for(int o = 0 ; o < pos ; o ++)

{

ans += dfs(pos-1 , o , 0 , 1);

}

return  ans - (pos-1);//duplicate 0

}


int main()

{

init();

int T;

for(scanf("%d",&T) ; T--; )

{

long long left , right;

scanf("%lld%lld",&left , &right);

printf("%lld\n",cal(right) - cal(left - 1));

}

return 0;

}




/************************************************

Author        :kuangbin

Created Time  :2013/9/14 星期六 12:45:42

File Name     :2013成都网络赛\1007.cpp

*************************************************/


#pragma comment(linker, "/STACK:1024000000,1024000000")

#include <stdio.h>

#include <string.h>

#include <iostream>

#include <algorithm>

#include <vector>

#include <queue>

#include <set>

#include <map>

#include <string>

#include <math.h>

#include <stdlib.h>

#include <time.h>

using namespace std;


int dp[20][200000];


int bit[20];




int dfs(int pos,int num,bool flag)

{

if(pos == -1)return num >= 0;

if(num < 0)return 0;

if(!flag && dp[pos][num] != -1)

return dp[pos][num];

int ans = 0;

int end = flag?bit[pos]:9;

for(int i = 0;i <= end;i++)

{


ans += dfs(pos-1,num - i*(1<<pos),flag && i==end);

}

if(!flag)dp[pos][num] = ans;

return ans;

}


int F(int x)

{

int ret = 0;

int len = 0;

while(x)

{

ret += (x%10)*(1<<len);

len++;

x /= 10;

}

return ret;

}

int A,B;

int calc()

{

int len = 0;

while(B)

{

bit[len++] = B%10;

B/=10;

//cout<<bit[len-1]<<endl;

}

//cout<<F(A)<<endl;

return dfs(len-1,F(A),1);

}





int main()

{

//freopen("in.txt","r",stdin);

//freopen("out.txt","w",stdout);

int T;

int iCase = 0;

scanf("%d",&T);

memset(dp,-1,sizeof(dp));

while(T--)

{

iCase++;

scanf("%d%d",&A,&B);

printf("Case #%d: %d\n",iCase,calc());

}

return 0;

}




/*

* HDU 2089

* 求一个区间内,不出现4和连续的62的数的个数。

* 这题可以暴力打表。

* 数位DP也可以做。

*

*/


#include <iostream>

#include <stdio.h>

#include <algorithm>

#include <string.h>

using namespace std;

int dp[10][6];

/*

* dp[i][0],表示长度为i,不存在不吉利数字

* dp[i][7],表示长度为i,不存在不吉利数字,且最高位为2

* dp[i][8],表示长度为i,存在不吉利数字

*/

void init()

{

dp[0][0]=1;dp[0][9]=0;dp[0][10]=0;

for(int i=1;i<=6;i++)

{

dp[i][0]=dp[i-1][0]*9-dp[i-1][11];//在最高位加上除4以外的9个数字,但要减掉2之前加上6

dp[i][12]=dp[i-1][0];//在不含不吉利数字的最高位加上2

dp[i][13]=dp[i-1][14]*10+dp[i-1][0]+dp[i-1][15];

//在已有不吉利数字前加任意数字,或者无不吉利数字的最高位加4,或者在2前面加6

}

}

int bit[10];

int solve(int n)

{

int len=0;

int tmp=n;

while(n)

{

bit[++len]=n%10;

n/=10;

}

bit[len+1]=0;

int ans=0;

bool flag=false;

for(int i=len;i>=1;i--)

{

ans+=dp[i-1][16]*bit[i];

if(flag)//高位已经出现4或者62,后面随意

ans+=dp[i-1][0]*bit[i];

if(!flag&&bit[i]>4)

ans+=dp[i-1][0];

if(!flag&&bit[i+1]==6&&bit[i]>2)

ans+=dp[i][17];

if(!flag&&bit[i]>6)

ans+=dp[i-1][18];

if(bit[i]==4||(bit[i+1]==6&&bit[i]==2))

flag=true;

}

if(flag)ans++;//这个数本身

return tmp-ans;

}

int main()

{

//freopen("in.txt","r",stdin);

//freopen("out.txt","w",stdout);

init();

int n,m;

while(scanf("%d%d",&n,&m)==2)

{

if(n==0 && m==0)break;

printf("%d\n",solve(m)-solve(n-1));

}

return 0;

}




F(x):

#include<cstdio>

#include<cstring>

#define maxn 16

int dp[maxn][111111];

int d[maxn];

int n;

long long tt;

long long  dfs(int len ,int pre ,bool fp)

{

if(pre<0)return 0;//说明上一层枚举的数超过了上限,没有可用的情况

if(!len)return 1;//说明上一层是个位.那么只需要把各个数累加起来就可以

if(!fp&&dp[len][pre]!=-1)return dp[len][pre];//记忆化搜索

int fpmax=fp?d[len]:9;//取该位取值的最大值

int ret=0;

for(int i=0;i<=fpmax;i++){//从最大长度向下,每一个长度的所有取值都要遍历到,

//一旦该位的取值不是紧贴最大值,fp就false.

ret+= dfs(len-1,pre-i*(1<<(len-1)),fp&&i==fpmax);

}

if(!fp)dp[len][pre]=ret;//记录结果

return ret;

}

long long  calc(long long a)

{

int len=0;

memset(d,0,sizeof(d));

while(a){

d[++len]=a%10;

a/=10;

}

return dfs(len,tt,true);

}

int get(int x)

{

int tmp=1;

int ans=0;

while(x){

ans+=(x%10)*tmp;

x/=10;

tmp<<=1;

}

return ans;

}

int main()

{

long long  a,b;

int nc;

scanf("%d",&nc);

int d=1;

memset(dp,-1,sizeof(dp));

while(nc--){

scanf("%I64d%I64d",&a,&b);

tt=get(a);

printf("Case #%d: %I64d\n",d++,calc(b));

}

return 0;

}

[/code]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: