24-3-15数位dp+dp优化笔记

windy数 

Windy 定义了一种Windy数:不含前导零且相邻两个数字之差至少为2的正整数被称为Windy
数。

Windy想知道,在A和B之间,包括A和B,总共有多少个Windy数?

输入格式

共一行,包含两个整数A和B。

输出格式
输出一个整数,表示答案。

数据范围
1<A<B<2×109

思路:设立f[i][j]下标为总位数和最高位的数字,值为该数字下的所有满足条件的方案数。solve函数将x的每一位数放入dig数组,先看位数,分为最高的位数小于上界和等于上界的情况,小于的全部累加,等于的从1到上界数字-1累加。再看位数已满、最高位的数字取得上界的情况,位置从次高位到位数为1遍历,检查该位与高一位数字是否满足条件,符合就加入答案。

主函数初始化符合该位与低一位数字满足条件的所有情况,代码“f[i][j]+=f[i-1][k];”。最后用前缀和思想“solve(b)-solve(a-1)”实现从a到b区间。

#include<bits/stdc++.h>
using namespace std;
long long f[20][20],dig[20];//长度,最高位数字
long long solve(long long x){//处理1-x
	memset(dig,0,sizeof(dig));
	long long cnt=0;
	while(x){
		dig[++cnt]=x%10;//每一位的最大数字
		x/=10;
	}
	long long ans=0;
	for(int i=1;i<cnt;i++){//最高位小于上界数字所有的都加上,位数低
		for(int j=1;j<=9;j++){//加了9次
			ans+=f[i][j];
			cout<<ans;
		}
	}//f[1][1-9]的累加

	for(int i=1;i<dig[cnt];i++){
		ans+=f[cnt][i];
		cout<<ans;//输入为25时,f[2][1]=7
	}//最高位数相同,最高位更小的所有情况

	//少了这个输出无1717171718,即11到13都不加,dig[1]为5,dig[2]为2,14和10f为1,因4为1
    for(int i=cnt-1;i>=1;i--){//从次高位到低开始枚举
	//所填数字的次高位小于上界数字的次高位
		for(int j=0;j<dig[i];j++){
			if(abs(j-dig[i+1])>=2)ans+=f[i][j];
			cout<<ans;//加上该位数字距高一位数字为2的情况
		}
		if(abs(dig[i+1]-dig[i])<2)break;
	}
	return ans;
}
int main(void){
	long long a,b;
	cin>>a>>b;
	for(int i=0;i<=9;i++)f[1][i]=1;//初始化0-9都有1个windy数
	for(int i=2;i<=9;i++){//位数从低到高
	//用来预处理可能出现的情况,以提高计算效率
	//只判断了该位与低一位符合时,该位将低一位方案数加上
		for(int j=0;j<=9;j++){//建立两个变量判断该位
			for(int k=0;k<=9;k++){
				if(abs(j-k)>=2)f[i][j]+=f[i-1][k];//将低位的加入
			}
		}
	}
	printf("%lld",solve(b)-solve(a-1));
	for(int i=1;i<=2;i++){
		for(int j=0;j<=9;j++){
			printf("%d ",f[i][j]);
		}
		printf("\n");
	}
}//输入10 25时,输出
//12345678916171717171812345678101 1 1 1 1 1 1 1 1 1
//8 7 7 7 7 7 7 7 7 8

计算在给定范围内(从1到指定的整数n)不包含数字4的数字数量

#include<bits/stdc++.h>
using namespace std;
vector<<vector>int>dp;
vector<int>digits;
int count(int pos,bool tight){
	if(pos==-1)return 1;//递归到头,将1放在不存在的位置上
	if(!tight&&dp[pos][tight]!=-1)return dp[pos][tight];//记忆化呼应
	int upper=tight?digits[pos]:9;
	int total=0;
	for(int digit=0;digit<=upper;digit++){
		if(digit==4)continue;
		total+=count(pos-1,tight&&(digit==upper));//除了4,循环到底一定tight为true
	}
	if(!tight)dp[pos][tight]=total;//记忆化伏笔
	//当upper=4时,tight不会变1,pos没有更新,会无限在该层搜索
	return total;
}
int solve(int n){
	digits.clear();
	cnt=0;
	while(x){
		digits[++cnt]=x%10;
		x/=10;
	}
	dp.assign(digits.size(),vector<int>(2,-1));
	//将dp都赋成digit大小且大小为2的一维向量,其中每个值-1
	return count(digits.size()-1,true);
}
int main(void){
	int n;cin>>n;
	//最后pos: -1, tight: 0变到pos: -1, tight: 1,会让solve多出1
	//而pos不为-1, tight: 1时的结果是需要的
	int ans=solve(n)-solve(0);
	cout<<ans<<endl;
}

给n拆成三个不同且不含2和4的数,输出方案数

#include<bits/stdc++.h>
using namespace std;
int n, dp[7][2][2][2][2][2][2][50010], num[7], p[7];
int dfs(int len, bool limit1, bool limit2, bool limit3, bool ok1, bool ok2, bool ok3, int sum) {
    if (!len) return sum == n && ok1 && ok2 && ok3;//上面所有层ok满足
    if (~dp[len][limit1][limit2][limit3][ok1][ok2][ok3][sum]) 
		return dp[len][limit1][limit2][limit3][ok1][ok2][ok3][sum];
    //dp为0回到0,记忆化呼应
	int res = 0;
    int up1 = limit1 ? num[len] : 9;//len的实时更新使limit为1时的len不断缩小
    int up2 = limit2 ? num[len] : 9;
    int up3 = limit3 ? num[len] : 9;
    for (int i = 0; i <= up1; i++) {//i为第一个数len-1位的数字
        if (i == 2 || i == 4) continue;
        int d1 = ok1 ? 0 : i;//若上一层的i < j,则j从0开始看,此时已经不挨着最高位
        for (int j = d1; j <= up2; j++) {
            if (j == 2 || j == 4) continue;
            int d2 = ok2 ? 0 : j;
            for (int k = d2; k <= up3; k++) {
                if (k == 2 || k == 4) continue;
                if (sum + i * p[len - 1] + j * p[len - 1] + k * p[len - 1] > n) break;
                res += dfs(len - 1, limit1 && i == up1, limit2 && j == up2, limit3 && k == up3,
                    ok1 || i < j, ok2 || j < k, ok3 || i,
                    sum + i * p[len - 1] + j * p[len - 1] + k * p[len - 1]);
            }
        }
    }
    return dp[len][limit1][limit2][limit3][ok1][ok2][ok3][sum] = res;//记忆化初始
}
int solve(int x) {
    memset(dp, -1, sizeof(dp));
    int len = 0;
    while (x) {
        num[++len] = x % 10;
        x /= 10;
    }
    return dfs(len, 1, 1, 1, 0, 0, 0, 0);//设1用&&,设0用||
	//limit设1原因在于最上层挨近高数位,需取数的最大值
	//ok为0防止最上层直接return
}
signed main() {
    p[0] = 1;
    for (int i = 1; i <= 6 ; i++) p[i] = p[i - 1] * 10;
    cin >> n;
    cout << solve(n) << '\n';
    return 0;
}

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值