UVA - 11038 How Many O's?

题目

用了数位dp递推的方式。

思路:

dp[i][j] 表示以i开头j位的0数。

cnt[i][j] 表示以i开头j位的数总数。

dp[i][j] = dp[t][j - 1] (0 <= t < =9);

如果i == 0的话还要加上\sum _{t = 0}^{9} cnt[t][j - 1];

这样DP数组就求出来了,接下来的问题就是怎么计算。

与一般数位DP不同的是,假如之前有t位是0的话,这一位的结果还要加上cnt[i][j - 1] 其中i小于这一位。(想一想,为什么?)

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;

typedef vector<int> vi;
typedef vector<vi> vii;
typedef vector<ll> vl;
typedef vector<vl> vll;
typedef vector<double> vd;
typedef vector<vd> vdd;
typedef pair<int, int> ii;

const ll MOD = 1e9 + 7;
const double eps = 1e-6;
const ll INF = 0x3f3f3f3f;
const double PI = acos(-1.0);

ll n, m, k;

vll dp, cnt;

ll count(int dit, int first){
	if(!dit)
		return cnt[first][dit] = 0;
	if(dit == 1)
		return cnt[first][dit] = 1;
	if(cnt[first][dit] >= 0)
		return cnt[first][dit];
	ll res = 0;
	for(int i = 0; i < 10; i++)
		res += count(dit - 1, i);
	return cnt[first][dit] = res;
}

ll recur(int dit, int first){
	if(!dit)
		return dp[first][dit] = 0;
	if(dit == 1)
		return dp[first][dit] = !first;
	if(dp[first][dit] >= 0)
		return dp[first][dit];
	ll res = 0;
	for(int i = 0; i < 10; i++)
		res += recur(dit - 1, i);
	if(!first)
		res += cnt[first][dit];
	return dp[first][dit] = res;
}

ll cal(ll x){
	if(x < 1)
		return 0;
	vi dit;
	while(x){
		dit.push_back(x % 10);
		x /= 10;
	}
	ll res = 0;
	for(int i = 1; i < (int)dit.size(); i++)
		for(int j = (i == 1 ? 0 : 1); j < 10; j++)
			res += dp[j][i];
	ll t = 0;
	for(int i = dit.size(); i > 0; i--){
		ll num = 0;
		for(int j = (i == (int)dit.size() && (int)dit.size() > 1); j < dit[i - 1]; j++){
			res += dp[j][i];
			num += cnt[j][i];
		}
		res += t * num;
		t += !dit[i - 1];
	}
	return res;
}

int main(void)
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout << setprecision(10) << fixed;
	cnt.resize(20, vl(20, -1));
	dp.resize(20, vl(20, -1));
	for(int i = 0; i < (int)dp.size(); i++)
		for(int j = 0; j < 10; j++){
			count(i, j);
			recur(i, j);
		}
	while(cin >> n >> m && m >= 0)
		cout << cal(m + 1) - cal(n) << endl;
	cerr << "execute time: " << (double)clock() / CLOCKS_PER_SEC << endl;
	return 0;
}

 

未来可期。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值