完美的消除

定义数的消除操作为选定[L,R,x],如果数的第L到第R位上的数字都大于等于x,并且这些数都相等,那么该操作是合法的(从低位到高位编号,个位是第一位,百位是第二位……),然后将这些位数上的数减x;否则就是不合法的,不能进行操作。对一个数操作最少的次数使得这个数变成0,这个操作次数称为该数的最小操作数。如:1232的最小操作数为3,一个合法解是[2,2,1],[1,3,2],[4,4,1]。

求L~R中最小操作数为k的数的个数。

例如:132,需要操作3次才能变为0。而131131 => 111131 => 111111 =>0

Input

单组测试数据。
三个整数L、R和k(1<=L<=R<=10^18,1<=k<=18)。

Output

一个整数表示答案。

Input示例

10 21 2

Output示例

9

思路:

消除一个数的话考虑按照数位的方式。这里需要用到0-9构成的单调递增栈。

1:比如数字123, 按数位看的话是一个单调递增栈,需要操作3次才能完全消除;

2:比如数字121,在最后一个1加入栈时,发现前面有2比它大,这时要将2消除,但因为之前第一位是1已经加入栈里,所以这个1就不再进栈。共需要2次操作可以完全消除。

3:比如数字221,在第二个2进栈时,发现前面有个2进栈,因此不需入栈,在最后一个1进栈时,将前面的2消除,然后1进栈。共需要2次操作可以完全消除。

因为数位只有0-9共十个数字,所以可以用十个二进制数来表示进栈出栈。

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;

const int MAXN = 30;
typedef long long ll;

int a[MAXN];
ll L, R, k, dp[20][1 << 10][20];

int cal(int p, int i) 
{
    for (int j = i + 1; j <= 9; j++)
    {
        if (p & (1 << j)) 
        {
            p ^= (1 << j);
        }
    }
    
    return p;
}

ll dfs(int pos, int p, int t, bool limit)
{
    if (pos < 0)
    {
        return t == k;
    }
    if (!limit && dp[pos][p][t] != -1)
    {
        return dp[pos][p][t];
    }

	int up = limit? a[pos] : 9;
	ll result = 0;
	for (int i = 0; i <= up; i++)
	{
		if (p & (1 << i) || i == 0)
		{
			result += dfs(pos - 1, cal(p, i), t, limit && i == a[pos]);
		}
		else
		{
			result += dfs(pos - 1, cal(p | (1 << i), i), t + 1, limit && i == a[pos]);
		}
	}

	if (!limit)
	{
		dp[pos][p][t] = result;
	}

	return result;
}

ll solve(ll x)
{
    memset(dp, -1, sizeof(dp));
    int pos = 0;
    while (x)
    {
        a[pos++] = x % 10;
        x /= 10;
    }
    return dfs(pos - 1, 0, 0, true);
}

int main()
{
    cin >> L >> R >> k;
    cout << solve(R) - solve(L - 1);
   
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值