数位dp笔记

``我太菜了。
放几个例题慢慢品。
1.不要62
题意:输出一段区间上不出现62和4的数字总个数

#include<iostream>
using nemespace std;
int ini(int x){
 int k=0;
 while(x){
  num[++k]=x%10;
  x /= 10;
 }
//存个数字,位数为k
 //一开始看的是最高位,所以初始是达到上限的
 //一开始那个数位没有上一位,所以if6是0啦
 return dfs(k,0,1);
} 
//len是当前的位置,也就是数字长度,在后续dfs中逐渐减到0;if6的意思是该数的上一位是否为6
//maxn是判断上限是否达到,即搜索时最高位不是9,而是它最初给的那个值
//比如765,最高位搜不到9只能到7
int dfs(int len,bool if6,bool maxn){
 if(!len)return 1;
 int cnt=0;
 //没到上限的话后几位都是不受影响的,只是开头数字不同而已,所以个数相同
 if(!maxn && dp[len][if6]) return dp[len][if6];
 int m=(maxn? num[len]:9);
 for(int i=0;i<=m;i++){
  if(i==4 || if6 && i==2) continue;
  //呐 i=m的时候就是到上限啦
  cnt += dfs(len-1,i==6,maxn && i==m);
 }
 //这个更新 别忘了条件啊!!!是不断在更新的,我服了我自己了。老是错
 return maxn ? cnt:dp[len][if6]=cnt;
 
}
int main(){
 int n,m;
 while(cin>>n>>m){
  if(!n && !m)break;
  cout << ini(m)-ini(n-1)<<endl; 
 }
}

2.B数。
含有13且能被13整除的叫b…数
我对dp真的是一无所知,这里有两个判断条件,所以dp要开两重
还看到一种四维dp的(dp[len][mod][if13][pre])判13需要存两个数据
注意dp数组大小,找错找到怀疑人生。

int dfs(int len, int sta, int yu, int maxn) {
 if (len == 0) {
  return yu%13==0 &&sta == 2;
 }
 int cnt = 0;
 int lim = maxn ? num[len]:9;
 if (!maxn && ~dp[len][sta][yu]) return dp[len][sta][yu];
 for (int i = 0;i <= lim;i++) {
 //gett(int sta,int i)即判断i时的sta新情况
  cnt+=dfs(len - 1, gett(sta,i), (yu * 10 + i) % 13, maxn&&i == lim);
 }
 if (!maxn) dp[len][sta][yu]= cnt;
 return cnt;
}

3.windy数
就是求相邻数位绝对值差小于2的数的个数,注意输入数据中含有前导零的项。
dfs的时候得处理一下。(处理一次就ok了其实,看起来挺复杂滴)
不知道直接读string再处理会不会简单一些啊
dp[i][j]数组长度为i,最高为为j时的数字个数
法(一) dfs

long long dfs(int len, int pre, bool f, bool maxn) {
 
 int lim = maxn ? num[len] : 9;
 if (!maxn && f && dp[len][pre] != -1) {
  return dp[len][pre];//未达到上限且没有前驱0
 }
 if (len <=0) {
  return 1;//一位数都符合
 }
 int cnt = 0;
 for (int i = 0;i <= lim;i++) {
  if (!f) {
  //如果有前驱0 那么后面一位不受控 从碰到第一个不为0的数开始合法,一直合法。。
   cnt += dfs(len - 1, i, i!=0 , maxn && i == lim);
  }
  //没有前驱 正常操作
  else if (abs(i - pre) >= 2) cnt += dfs(len - 1, i, 1, i == lim && maxn);
 }
 //如果未达到上限且没有前驱,值得记录
 if (!maxn && f) dp[len][pre] = cnt;
 return cnt;
}

那么我们dfs默认是存在前导0的。dfs(len,0,0,1)–dfs(当前长度,前一个数字,合法否,上限否)

法(二)dp计算
分情况讨论 比如数 96758
ans = 位数小于5时的所有情况 + 位数等于5但首位小于9的所有情况 + 位数等于5且首位9后面的情况
dp[5][9] = dp[1-4][1-9]+dp[5][1-8]+讨论
第三块的讨论nie
首先首位为9 讨论第二位1-6都行 那么ans+=dp[6所在位数][1-9]
看看第三位 啊 如果还要一样的话就不满足条件了 所以我们到此为止。。讨论结束
同理,就是一位一位的覆盖逼近原数,一旦不满足条件就退出循环。
初始化 呐 位数为1的dp都是1
然后枚举初始化 三个for注意循环顺序
0:初始化

void ini() {
 //一位数都符合
 memset(dp, 0, sizeof(dp));
 for (int i = 0;i <= 9;i++) {
  dp[1][i] = 1;
 }
 //从二位起,枚举。
 for (int i = 2;i <= 10;i++) {
  for (int j = 0;j <= 9;j++) {
   for (int k = 0;k <= 9;k++) {
    if (abs(j - k) >= 2) {
     dp[i][j]+= dp[i - 1][k];
    }
   }
  }
 }
}

情况1 位数相同,首位不同

 for (int i = 1;i <= num[k]-1;i++) {
  ans += dp[k][i];
 }

情况2 位数比现在小,首位随便乱来(x

 for (int i = k - 1;i >= 1;i--) {
  for (int j = 1;j <= 9;j++) {
   ans += dp[i][j];
  }
 }

情况3 位数同,首位同,一位一位找

for(int i = k - 1;i >= 1;i--) {
  for (int j = 0;j <= num[i]-1;j++) {
   if (abs(num[i+1] - j) >= 2) {
    ans += dp[i][j];
   }
  }
  //跳出条件
  if (abs(num[i] - num[i + 1]) < 2)break;
 }

3.Round numbers
要求换成二进制后的里面0的个数大于等于1
法一 组合数学
法二 dp
哇 看了这么多题终于自己做出来了一道。。

//主要是处理前导0的问题 一个数据也就处理一次 以及注意dp数组大小,我这个缺心眼的又爆了一次。
int dfs(int len, int o, int z,bool f, int maxn) {
 if (len <= 0) return  o <= z;
 int cnt = 0;
 int lim = maxn ? num[len] : 1;
 if (!maxn && dp[len][o][z] != -1) return dp[len][o][z];
 for (int i = 0;i <= lim;i++) {
  if (!f) {
  //如果有前导0 那么前面的什么都是假的。清零
   if (i==1) {
    cnt += dfs(len - 1, 1, 0, 1, maxn&&i == lim);
   }
   else cnt += dfs(len - 1, 0, 0, 0, maxn&&i == lim);
  }
  //常规操作
  else {
   if(i==1) cnt += dfs(len - 1, o + 1, z, 1, maxn&&i == lim);
   else cnt += dfs(len - 1, o , z+1, 1, maxn&&i == lim);
  }
 }
 if (!maxn) dp[len][o][z] = cnt;
 return cnt;
}

4.F(x)
题意:每位加权2^(len)次方 求0-y范围内小于x的个数

有一个奇妙的操作,就是求差。
呐,为什么不能求和呢?要判断sum<=f(x),如果要用dp那么要开dp[len][sum][f(x)]
会爆。
so 我们求差,求差的好处,len层层递减,从dp[len][f(x)]算下去
若要二维dp求和,那么就变成dp[len][0]算上来 那样后面的还没初始化就被参与了运算,dp失败。。
所以想要求和,得开三维,从最外层开始算(码一下,慢慢研究,一知半解来的

扫描二维码关注公众号,回复: 10359894 查看本文章
int dfs(int len, int sum,bool maxn) {
 if (len <= -1) return sum>=0;
 if (sum < 0) return 0;
 if (!maxn && dp[len][sum] != -1)return dp[len][sum];
 int lim = maxn ? num[len] : 9;
 int cnt = 0;
 for (int i = 0;i <= lim;i++) {
  cnt += dfs(len - 1, sum - i * pow(2, len), i ==lim && maxn);
 }
 if (!maxn) dp[len][sum] = cnt;
 return cnt;
}
发布了24 篇原创文章 · 获赞 2 · 访问量 971

猜你喜欢

转载自blog.csdn.net/weixin_43521836/article/details/88955293