数位dp练习

https://acm.hdu.edu.cn/showproblem.php?pid=3555

解法1:求多少数包含“49”

#include <bits/stdc++.h>
using namespace std;
#define int long long //注意!!本题数据类型为ll 
const int N=20;
//dp[pos][sta]表示当前第pos位,sta状态下满足条件的个数,sta表示前一位是否是4,只有0和1两种状态
int dp[N][2], num[N];
int t, n;
int z[N];

int dfs(int pos, bool sta, bool limit = true) {
    if (pos == 0) return 0; // 如果已经处理完所有位,返回
    if (!limit && dp[pos][sta] != -1) return dp[pos][sta]; // 如果不是限制状态且已经计算过,直接返回结果

    int res = 0;
    int up = limit ? num[pos] : 9; // 如果是限制状态,当前位的上限为num[pos],否则为9
    for (int i = 0; i <= up; i++) {
        if(i==9  && sta) 
        {
            if(limit)
                res += n%z[pos-1]+1; // 如果是限制状态, 就加上剩余的可能的数 [0, abcd]
            else 
                res += z[pos - 1]; // 如果不是限制状态,剩余的数字可以随便选 [0, 9999...]

        }
        else
            res += dfs(pos - 1, i==4, limit && (i == up)); // 递归处理下一位
    }

    if (!limit) dp[pos][sta] = res; // 如果不是限制状态,保存结果
    return res;
}

void solve(int n) {
    memset(dp, -1, sizeof(dp));
    int len=0;
    while(n){
        num[++len]=n%10;
        n/=10;
    }
    cout<< dfs(len, 0, true)<<'\n';
}
signed main()
{
    z[0]=1;
	for(int i=1;i<N;i++)
	    z[i]=z[i-1]*10;
    cin >> t;
    while (t--) {  
        cin>>n;
        solve(n);
    }
    return 0;
}

解法2: 求多少数不包含“49”

注意: 默认dfs是包含0的,要减去

#include<cstdio>//求不包含49的个数ans(不包括0),然后n-ans
#include<cstring>
using namespace std;
typedef long long LL;//注意!!本题数据类型为ll 
const int N=30;
int dig[N];
LL dp[N][2],n;
//dp[pos][sta]表示当前第pos位,sta状态下满足条件的个数,sta表示前一位是否是4,只有0和1两种状态
LL dfs(int pos,bool sta,bool limit)//求不包含49的个数
{
    if(!pos) return 1;//包括0 
    if(!limit&&dp[pos][sta]!=-1) return dp[pos][sta];
    int len=limit?dig[pos]:9;
    LL ans=0;
    for(int i=0;i<=len;i++)
    {
        if(sta&&i==9)
        	continue;
        ans+=dfs(pos-1,i==4,limit&&i==len);
    }
    if(!limit) dp[pos][sta]=ans;
    return ans;
}

LL solve(LL x)//求解[1..x]之间不包含49的个数 
{
    int pos=0;
    while(x){
        dig[++pos]=x%10;
        x/=10;
    }
    return dfs(pos,0,1)-1;//*** 除去0 ***
}

int main()
{
    memset(dp,-1,sizeof(dp));
	int T;
	scanf("%d",&T);
	while(T--){
		scanf("%lld",&n);
		printf("%lld\n",n-solve(n));
	}
    return 0;
}

P13085 [SCOI2009] windy 数(加强版)
https://www.luogu.com.cn/problem/P13085
判断前一位数字

#include <bits/stdc++.h>
using namespace std;
const int N=20;
using ll=long long;
ll dp[N][N], num[N];
ll a, b;
ll dfs(int pos, int pre, bool lead, bool limit){
    if(pos==0){
        return 1;
    }
    if(!lead&&!limit&&dp[pos][pre]!=-1) return dp[pos][pre]; 
    int up=limit?num[pos]:9;
    ll ans=0;
    for(int i=0;i<=up;i++){
        bool nlead=lead&&i==0;
        bool nlimit=limit&&i==up;
        if(nlead){// 0开头
            ans+=dfs(pos-1, -2, nlead, nlimit);
            continue;
        }
        if(abs(i-pre)<2) continue;
        ans+=dfs(pos-1, i, nlead, nlimit);
    }
    if(!lead&&!limit) dp[pos][pre]=ans; 
    return ans;
}

ll solve(ll x){
    int len=0;
    while(x){
        num[++len]=x%10;
        x/=10;
    }
    memset(dp, -1, sizeof dp);
    return dfs(len, -2, 1, 1);
}
int main()
{
    cin>>a>>b;
    cout<<solve(b)-solve(a-1);
    return 0;
}

P6218 [USACO06NOV] Round Numbers S

https://www.luogu.com.cn/problem/P6218

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

const int MAXL = 32;              // 二进制最多 31 位,我们多留一位
int num[MAXL];                    // 存放目标数的二进制位,1..len
ll dp[MAXL][MAXL][MAXL];          // 记忆化数组:pos, zero, one

/**
 * @brief 在二进制第 pos 位做决策
 * @param pos   剩余要处理的位数(从 len 开始到 1,再到 0)
 * @param zero  已经累计的“有效0”的个数
 * @param one   已经累计的“1”的个数
 * @param lead  是否还在“前导零”阶段(前导零不算入 zero)
 * @param limit 是否受上界限制(true 表示要 <= num[pos])
 * @return [0..] 这一状态下能凑出的合法圆数数量
 */
ll dfs(int pos, int zero, int one, bool lead, bool limit) {
    // 1)基准情况:所有位都填完
    if (pos == 0) {
        // 只有累计了至少一个 1(即 !lead)且 zero >= one 才是圆数
        return (!lead && zero >= one) ? 1 : 0;
    }
    // 2)记忆化:只有在“不受限” 且 “不在前导零” 时才 memo
    if (!lead && !limit && dp[pos][zero][one] != -1) {
        return dp[pos][zero][one];
    }

    ll res = 0;
    int up = limit ? num[pos] : 1;  // 如果受限,就最高到 num[pos];否则可以 0/1

    for (int bit = 0; bit <= up; ++bit) {
        bool nlead  = lead && (bit == 0);
        bool nlimit = limit && (bit == up);
        int nzero = zero, none = one;

        if (!nlead) {
            // 进入有效阶段后,放 0/1 都要计数
            if (bit == 0) nzero++;
            else          none++;
        }
        res += dfs(pos - 1, nzero, none, nlead, nlimit);
    }
    if (!lead && !limit) {
        dp[pos][zero][one] = res;
    }
    return res;
}

/**
 * @brief 计算 [0..x] 范围内的圆数个数
 */
ll solve(int x) {
    if (x <= 0) return 0;
    // 1) 拆二进制到 num[]
    int len = 0;
    while (x) {
        num[++len] = x & 1;
        x >>= 1;
    }
    // 2) 清空 dp 记忆表
    memset(dp, -1, sizeof dp);
    // 3) 从最高位 len 开始,zero=one=0,lead=1(还没出现过1),limit=1(受上界约束)
    return dfs(len, 0, 0, true, true);
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    int l, r;
    cin >> l >> r;
    // 区间答案 = solve(r) - solve(l-1)
    cout << (solve(r) - solve(l - 1)) << "\n";
    return 0;
}

题单:
https://www.luogu.com/article/qexjclet

posted @ 2025-07-22 17:46  katago  阅读(14)  评论(0)    收藏  举报