杠杆数/平衡数【数位dp】

传送门

题目描述

如果把一个数的某一位当成支点,且左边的数字到这个点的力矩和等于右边的数字到这个点的力矩和,那么这个数就可以被叫成杠杆数。

比如4139就是杠杆数,把3当成支点,我们有这样的等式:4 * 2 + 1 * 1 = 9 * 1。

给定区间[x,y],求出在[x,y]中有几个杠杆数。

输入格式

两个数,表示x,y。

输出格式

一个输出,表示区间[x,y]中杠杆数的个数。

输入输出样例

输入 #1
7604 24324
输出 #1
897

说明/提示

对于40%的数据,x<=y<=x+100000

对于100%的数据,1<=x<=y<=10^18

思路

数位dp,也叫试填法,一般应用于

  1. 求出在给定区间[A,B]内,符合条件P(i)的数i的个数.

  2. 条件P(i)一般与数的大小无关,而与 数的组成 有关(需要对一个数的每一位操作,因此数可能会很大甚至用字符串输入)

对于本题来说,相当于判断每个数字是否满足条件,可以用数位dp存储前缀有多少个满足条件的数字,用记忆化搜索保存

代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long 
ll T,dp[20][20][2005],num[205],l,r;
//dp[pos][x][num1]没有lim限制时,当前填到第pos位,支点为x,两边的贡献之和为num1时,有多少数字满足题意 
ll dfs(int pos,int x,bool lim,int num1)
{
    if(!pos) return num1==0;//边界 
    if(!lim && dp[pos][x][num1]!=-1) return dp[pos][x][num1];//没有限制才能直接返回 
    int maxx;
    if(!lim) maxx=9;
    else maxx=num[pos];
    ll ans = 0;
    for(int i=0;i<=maxx;i++)    
        ans += dfs(pos-1,x,lim&&i==maxx,num1+(x-pos)*i);
    if(!lim) dp[pos][x][num1]=ans;
    return ans;
}
ll solve(ll x)//[0,x]区间满足条件的数字数量 
{
    if(x == -1)return 0;
    ll ans=0;int cnt=0;
    while(x) num[++cnt]=x%10,x/=10;
    for(int i=1;i<=cnt;i++) ans+=dfs(cnt,i,1,0);
    return ans-cnt+1;//00000000(所有位置都为0被重复算了cnt次,有一次满足题意) 
}

int main()
{
//    scanf("%lld",&T);
    memset(dp,-1,sizeof(dp));
//    while(T--)
    {
        scanf("%lld%lld",&l,&r);
        printf("%lld
",solve(r)-solve(l-1)); //类似前缀和 
    }
    return 0;
}
原文地址:https://www.cnblogs.com/conprour/p/14838878.html