51nod 1623:完美消除

基准时间限制:1 秒 空间限制:131072 KB 分值: 80  难度:5级算法题
 收藏
 关注

定义数的消除操作为选定[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

明显的数位dp,但是做的时候不知道如何求1到x里面符合要求的个数有多少个。

下面是官方题解:(表示维护一个栈的思路简直了,又被自己低智商给感动了)

对于一个数计算最小操作数:
维护一个栈。
从高位到低位依次考虑每一位,设当前数字为x,将栈里所有大于x的数字删除,如果此时栈里没有数字x则加入,并且答案+1。
用一个二进制数来表示栈里有哪些元素。
因为可以按位考虑所以可以用数位dp来做这个问题。
最后答案等于1~R里符合要求的数量-1~L-1里符合要求的数量。所以这里只考虑1~R的计算。
设数字R有n位,a[i]表示第i位。(从低位到高位编号,个位是第一位,百位是第二位……)
设某数第i位的数字为x,且x<a[i],且第i+1位~第n位数字与R相同,将这个数记为(i,x),
可以通过这个标准将数分类,最多18*9种分类。
对于一种分类,统计这种分类里有几个数符合题目要求。
计算出此时栈的状态和已累加的答案(即最小操作数),记为 t1,t2 , dp[t1][i−1][K−t2] 为这类里符合要求的数的数量(K为输入要求的最小操作数)。
dp[i][j][k] 表示当前栈里的元素状态为i,再填j位数字,使答案(即最小操作数)刚好再加k的方案数,这个可以通过枚举下一位数字转移得到。
总复杂度O(18*512*18*10)

代码:

//#pragma comment(linker, "/STACK:102400000,102400000") 
#pragma warning(disable:4996)
#include <fstream>
#include <iostream>
#include <functional>
#include <algorithm>
#include <cstring>
#include <vector>
#include <string>
#include <cstdio>
#include <cmath>
#include <queue>
#include <stack>
#include <deque>
#include <ctime>
#include <set>
#include <map>
using namespace std;
typedef long long ll;

#define eps 1e-10
#define LL_INF 0x3fffffffffffffff
#define INF 0x3f3f3f3f
#define mem(a, b) memset(a, b, sizeof(a))
#define pper(i,n,m) for(int i = n;i >= m; i--)
#define repp(i, n, m) for (int i = n; i <= m; i++)
#define rep(i, n, m) for (int i = n; i < m; i++)
#define sa(n) scanf("%d", &(n))
#define mp make_pair
#define ff first
#define ss second
#define pb push_back

const int maxn = 8005;
const ll mod = 1e9 + 7;
const double PI = acos(-1.0);

ll L, R, k;
ll res[20][20][1050];
int dig[20];

int change(int s, int x)
{
	int i, j, k;
	for (i = 0; i <= 9; i++)
	{
		if ((i > x) && (s >> i & 1))
		{
			s ^= (1 << i);
		}
	}
	return s | (1 << x);
}

ll dfs(int len, int num, int sta,int up)
{
	if (len == 0)
	{
		return num == k;
	}
	if (!up&&res[len][num][sta] != -1)
	{
		return res[len][num][sta];
	}
	int u = up == 1 ? dig[len] : 9;
	int i, j, k;
	ll ans = 0;
	for (i = 0; i <= u; i++)
	{
		if (sta >> i & 1)
		{
			ans += dfs(len - 1, num, change(sta, i), i == u&&up);
		}
		else
		{
			ans += dfs(len - 1, num + 1, change(sta, i), i == u&&up);
		}
	}
	if (!up)
		res[len][num][sta] = ans;
	return ans;
}

ll solve(ll x)
{
	mem(res, 0);
	int len = 0;
	while (x)
	{
		len++;
		dig[len] = x % 10;
		x /= 10;
	}
	mem(res, -1);
	return dfs(len, 0, 1, 1);
}

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



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值