数位dp
Posted pywbktda
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数位dp相关的知识,希望对你有一定的参考价值。
例题1:[bzoj1833]数字计数(模板题)
题意:求区间内0-9各出现了几次
1.做一个差分,[a,b]的统计可以理解为[1,b]-[1,a)的统计,问题转化为求前缀的0-9个数
2.要对0-9每一个数字分别dp(也可以dp多加一维状态),问题转化为求前缀的1个数(以1为例)
3.数位dp+记忆化搜索,记录五个状态(具体见代码)(还有一种dp预处理+乱搞计算,比较复杂其实是我太菜了)
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define ll long long 4 int a[21]; 5 ll x,y,f[21][11][21];//f[i][j][k]表示之前已经有k个j,最后i位任意填,一共有几个j(本题中可以优化,但大部分题目不能) 6 ll dfs(int k,int t,int s,int p1,int p2)//k表示dp到第i位(从高到低),t表示统计t,s表示已经有多少t,p1表示是否有非0数,p2表示是否能取到上限 7 if (!k)return s;//dp完成,返回t的个数 8 if ((p1)&&(p2)&&(f[k][t][s]))return f[k][t][s];//记忆化 9 int ma=9;//通常可以取0-9 10 if (!p2)ma=a[k];//有限制 11 ll ans=0;//答案清0 12 for(int i=0;i<=ma;i++)ans+=dfs(k-1,t,s+((i==t)&&((t)||(p1))),p1|(i>0),p2|(i<ma));//转移到下一个状态(写得比较丑) 13 if ((p1)&&(p2))f[k][t][s]=ans;//存储答案 14 return ans;//返回答案 15 16 ll calc(ll k,int p) 17 a[0]=0; 18 memset(f,0,sizeof(f));//初始化 19 while (k)//取出k的每一位(因为要从高到低) 20 a[++a[0]]=k%10; 21 k/=10; 22 23 return dfs(a[0],p,0,0,0);//记忆化搜索 24 25 int main() 26 scanf("%lld%lld",&x,&y); 27 for(int i=0;i<9;i++)printf("%lld ",calc(y,i)-calc(x-1,i));//差分 28 printf("%lld",calc(y,9)-calc(x-1,9)); 29
例题2:[hdu2089]不要62(需要记录其他状态)
题意:多组数据,求区间不含62或4的数个数
1.同样做差分,转换为前缀
2.在上一题的基础上,记录上一个数字即可(详见代码)
1 #include<bits/stdc++.h> 2 using namespace std; 3 int x,y,a[11],f[11][2];//f[i][j]表示任意i位数上一位状态为j(是否等于6)的合法数数量 4 int dfs(int k,int t,int p)//k表示dp到第k位,t表示上一个数字是否为6,p表示是否要小于等于ai 5 if (!k)return 1;//1个合法数字 6 if ((p)&&(f[k][t]))return f[k][t];//记忆化 7 int ma=9,ans=0;//清空答案 8 if (!p)ma=a[k];//求出上限 9 for(int i=0;i<=ma;i++) 10 if ((i!=4)&&((i!=2)||(!t)))ans+=dfs(k-1,i==6,p|(i<ma));//判断是否可行&累计答案 11 if (p)f[k][t]=ans;//存储答案 12 return ans;//返回答案 13 14 int calc(int k)//计算1-k的答案 15 a[0]=0; 16 memset(f,0,sizeof(f));//清空 17 while (k)//转化为十进制(要从高到低) 18 a[++a[0]]=k%10; 19 k/=10; 20 21 return dfs(a[0],0,0);//记忆化搜索 22 23 int main() 24 while (scanf("%d%d",&x,&y)!=EOF) 25 if ((!x)&&(!y))return 0; 26 printf("%d\n",calc(y)-calc(x-1));//差分 27 28
类似题目:
1.hdu3555:记录上一个数
2.bzoj1026:记录上一个数
3.hdu3652:记录上一个数和余数
4.hdu3709:记录上一个数和权值差
例题3:[bzoj3329]Xorequ(二进制下的数位dp)
题意:t组数据,求小于等于n/2^n的满足x^3x=2x的数个数
1.问题转化为2x^x=3x(异或满足自反性),在分析发现x满足条件当且仅当二进制下没有相邻1
2.类似于上题(比上题还简单)的dp,第二问需要矩阵乘法优化dp,与数位dp无关故不讲
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define ll long long 4 #define mod 1000000007 5 struct ji 6 int a[2][2]; 7 ji operator * (const ji &b) 8 ji c; 9 for(int i=0;i<2;i++) 10 for(int j=0;j<2;j++) 11 c.a[i][j]=(1LL*a[i][0]*b.a[0][j]+1LL*a[i][1]*b.a[1][j])%mod; 12 return c; 13 14 p; 15 int t,ans,a[101]; 16 ll n,f[101][2]; 17 ll dfs(int k,int t,int p)//记忆化搜索 18 if (!k)return 1;//1种答案 19 if ((p)&&(f[k][t]))return f[k][t];//记忆化 20 int ma=1; 21 ll ans=0;//清空答案 22 if (!p)ma=a[k];//确定上限 23 for(int i=0;i<=ma;i++) 24 if ((!i)||(!t))ans+=dfs(k-1,i,p|(i<ma));//判断&统计答案 25 if (p)f[k][t]=ans;//存储答案 26 return ans;//返回答案 27 28 ll calc(ll k) 29 a[0]=0; 30 memset(f,0,sizeof(f));//清空答案 31 while (k)//转化为二进制 32 a[++a[0]]=k%2; 33 k/=2; 34 35 return dfs(a[0],0,0);//计算答案 36 37 ji ksm(ji n,ll m) 38 if (m==1)return n; 39 ji s=ksm(n,m>>1); 40 s=s*s; 41 if (m&1)s=s*n; 42 return s; 43 44 int main() 45 scanf("%d",&t); 46 while (t--) 47 scanf("%lld",&n); 48 printf("%lld\n",calc(n)-1);//计算答案,不能为0 49 if (n==1) 50 printf("2\n"); 51 continue; 52 53 p=ksm(ji1,1,1,0,n-1); 54 ans=0; 55 for(int i=0;i<2;i++) 56 for(int j=0;j<2;j++)ans=(ans+p.a[i][j])%mod; 57 printf("%d\n",ans); 58 59
其他题目:
1.poj3252:记录0和1的数量差(需要+40防止为负)
2.cf809C:发现规律——$A_i,j=(i-1)^(j-1)+1$(从1开始),然后把(i-1)^(j-1)和1分离计算即可
3.bzoj3209:枚举含有多少个1,然后即求二进制中有k个1的个数,数位dp
例题4:[hdu4352]XHXJ‘s LIS(数位dp+状压dp)
题意:t组数据,求区间内把一个数每一位分开后LIS为k的数个数
1.做差分转化为前缀问题
2.考虑LIS的一种求法:维护每一个长度的最小结尾,可以用这个序列来表示答案
3.这个序列只与每一个数字有没有出现有关,用2^10状压
4.必须要在记忆化中存储LIS的长度,否则每一次都清空会tle
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define ll long long 4 int t,k,a[21]; 5 ll l,r,f[21][11][2005];//f[i][j][k]表示i位数初始状态为k有多少方案LIS为j 6 int count(int k)//统计1的个数 7 int ans=0;//清空答案 8 while (k)//类似树状数组的统计方法 9 k&=k-1;//去掉最后一个1 10 ans++;//答案+1 11 12 return ans;//返回答案 13 14 ll dfs(int k,int t,int s,int p1,int p2)//k表示位数,t表示LIS的目标长度,s表示LIS的状态,p1表示是否为0,p2表示是否要求取到上限 15 if (!k)return count(s)==t;//判断LIS是否符合条件 16 if ((p1)&&(p2)&&(f[k][t][s]>=0))return f[k][t][s];//记忆化 17 int ma=9,la=-1,ss;//la表示上一个s状态中非0位置 18 ll ans=0;//清空答案 19 if (!p2)ma=a[k];//计算上限 20 for(int i=9;i>ma;i--) 21 if (s&(1<<i))la=i;//初始化la 22 for(int i=ma;i>=0;i--)//枚举下一位 23 if (s&(1<<i))la=i;//修改la 24 ss=s; 25 if ((p1)||(i))ss+=(1<<i); 26 if (la>=0)ss-=(1<<la);//修改LIS的状态 27 ans+=dfs(k-1,t,ss,p1|(i>0),p2|(i<ma));//累计答案 28 29 if ((p1)&&(p2))f[k][t][s]=ans;//存储答案 30 return ans;//返回答案 31 32 ll calc(ll k,int p)//计算前缀答案 33 for(a[0]=0;k;k/=10)a[++a[0]]=k%10;//转化为十进制 34 return dfs(a[0],p,0,0,0);//记忆化搜索 35 36 int main() 37 scanf("%d",&t); 38 memset(f,-1,sizeof(f));//清空,不能清为0,因为可能有很多答案都是0 39 for(int ii=1;ii<=t;ii++) 40 scanf("%lld%lld%d",&l,&r,&k); 41 printf("Case #%d: %lld\n",ii,calc(r,k)-calc(l-1,k));//差分 42 43
其他题目:
cf1073E:对每一个数字是否出现状压
好像就没什么题目了我太菜了
例题5:[cf908G]New Year and Original Order(优化/改变dp状态)
题意:f(x)表示将x各数位排序后的值,求小于等于n的x的f(x)之和,mod 1e9+7
1.发现0-9每一个数字相互独立,因此分别计算
2.状态中新增有多少个数字大于统计数字,多少个数字等于统计数字(小于可以计算得到),但状态太大
3.发现只需要统计大于等于该数字的数量,然后计算贡献即可,时间复杂度可以接受
PS:好像没人写记忆化?好吧那我也不写记忆化了……
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define mod 1000000007 4 int n,ans,f[1005][1005][11][2];//f[i][j][k][l]表示前i位,有j个数大于等于k,能否取到上限(l) 5 char s[1005]; 6 int main() 7 scanf("%s",s); 8 n=strlen(s); 9 for(int i=0;i<10;i++)f[0][0][i][0]=1;//0位数有0个数大于等于某数 10 for(int i=0;i<n;i++) 11 int p=(s[i]-‘0‘); 12 for(int j=0;j<=i;j++) 13 for(int k=0;k<10;k++) 14 for(int l=0;l<2;l++)//对应f状态的ijkl 15 for(int t=0;t<=max(p,9*l);t++)//枚举最后一位来转移 16 f[i+1][j+(k<=t)][k][l|(t<p)]+=f[i][j][k][l];//考虑对哪些状态有贡献来转移 17 f[i+1][j+(k<=t)][k][l|(t<p)]%=mod;//转移得到的状态 18 19 20 for(int i=1,s=0;i<=n;i++) 21 s=(10LL*s+1)%mod;//计算i个1的答案 22 for(int j=1;j<10;j++) 23 for(int k=0;k<2;k++)ans=(ans+1LL*s*f[n][i][j][k])%mod;//统计答案 24 25 printf("%d",ans); 26
hdu4734:发现f的值域约为1e4,存储状态(i,j,k)表示dp到i位,当前数是j,不能超过k,但这样会炸掉,发现答案只与j-k的差值有关,存储(i,j-k)即可
好像也没什么题目了还是我太菜了
总结(数位dp的基本套路):
1.差分,基本上所有区间的询问都要差分(也可能在二维甚至更高维差分)
2.记录有无前导0、是否能取到上限来判断(特殊题目可能还有别的)
3.多记录一些东西,用增加状态来减少清空复杂度(例4)
4.对0-9的数字分离,适用于数字间关系不大的情况
5.考虑优化/改变状态来达到节省空间/时间的作用(例5)
6.不要看到数位有关的问题就用数位dp(比如bzoj4292枚举f(n)即可)
以上是关于数位dp的主要内容,如果未能解决你的问题,请参考以下文章