//加了下面的代码,博客会禁止复制(代码还可以复制) // document.body.onselectstart = document.body.ondrag = function(){return false;}

数位统计DP

数位统计DP的递推实现

主要思想

定义状态\(dp[]\),\(dp[i]\)\(i\)位数的每种数字有多少个,说明如下。

(1)\(dp[i] = dp[i-1]\times 10 + 10^{i-1}\),这是从递推的角度分析得到的。

(2)\(dp[i] = i \times 10 ^{i} / 10\),这是按排列组合的思路得到的。

注意:

(1)特判当前的最高位。即“数位限制”。

(2)特判前导0。

例题1:数字计数

code
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
#define pb push_back
#define ff first
#define se second
#define pii pair<ll,ll>
const int maxn = 1e5+5;
ll dp[20];
ll num[15], ten[20];
ll cnta[20], cntb[20];
void init(){
	ten[0] = 1;
	for(int i = 1; i <= 15; ++i){
		dp[i] = i * ten[i-1];
		ten[i] = 10 * ten[i-1];
	}
}
void solve(ll x, ll *cnt){
	int len = 0;
	while(x){
		num[++len] = x % 10;
		x /= 10;
	}
	for(int i = len; i >= 1; --i){
		for(int j = 0; j <= 9; ++j){
			cnt[j] += dp[i-1] * num[i];
		}
		for(int j = 0; j < num[i]; ++j){
			cnt[j] += ten[i-1];
		}
		ll num2 = 0;
		for(int j = i - 1; j >= 1; --j){
			num2 = 10 * num2 + num[j];
		}
		cnt[num[i]] +=  num2 + 1;
		cnt[0] -= ten[i-1];
	}
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	ll a, b;
	cin  >>  a >> b;
	init();
	solve(a-1,cnta);
	solve(b,cntb);
	for(int i = 0; i <= 9; ++i){
		cout << cntb[i] - cnta[i] << ' ' ;
	}
	return 0;
}

数位统计DP的记忆化搜索

设计\(dp\)状态。和前面递推的编码类似,记忆化搜索的代码中也需要处理前导0和每位的最高位。编码时,每次统计\(0\sim9\)中的一个数字,代码中用变量\(now\)来表示这个数字。下面的解释都以\(now = 2\)为例。

\(dp[pos][sum]\)表示最后\(pos\)位范围是[0...0,99...9],前面2的个数为sum时,数字2的总个数。例如,dp[1][0]=1表示\(00\sim 09\),\(10\sim19\),\(30\sim39\),...区间内2的个数为1;dp[1][1]=11表示20~29区间内2的个数为11;dp[1][2] = 21表示220~229区间内2的个数为21等等。

用lead标识是否有前导0,lead = false 表示没有前导0,lead = true表示有前导0。

用limit标识当前最高位的情况,即“数位限制”的情况。如果是0~9,limit = false ;否则limit = true。例如[0,324],计算324的最高位时,范围是0~3,此时limit = true。再如,从最高位数字1递归到下一位时,下一位的范围是0~9,此时limit = false。

例题2:Windy数

code
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
#define pb push_back
#define ff first
#define se second
#define pii pair<ll,ll>
const int maxn = 15;
ll dp[15][15][2][2];
int num[15];
ll dfs(int pos, int last, bool lead, bool limit){
	ll ans = 0;
	if(pos == 0)
		return 1;
	if(dp[pos][last][lead][limit] != -1)
		return dp[pos][last][lead][limit];
	int up = (limit ? num[pos] : 9);
	for(int i = 0; i <= up; ++i){
		if(abs(last-i)>=2){
			if(i == 0 && lead)
				ans += dfs(pos-1, -2, true, limit && i == up);
			else{
				ans += dfs(pos-1, i, false, limit && i == up);
			}
		}
	}
	return dp[pos][last][lead][limit] = 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,true,true);
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	int a,b;
	cin >> a >> b;
	cout << solve(b) - solve(a-1);
	return 0;
}

例题3.手机号码

思路:
定义状态\(dpp[pos][u][v][state][n8][n4]\),其中pos表示当前数字长度,u表示前一位数字,v表示再前一位数字,state标识是否出现3个连续相同数字,n8标识是否出现8,n4标识是否出现4。

code
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
#define pb push_back
#define ff first
#define se second
#define pii pair<ll,ll>
const int maxn = 1e5+5;
int dp[15][15][15][2][2][2];
int num[15];
ll dfs(int pos,int u, int v, int state, int n8, int n4, int limit){
	ll ans = 0;
	if(n8 && n4)
		return 0;
	if(pos == 0)
		return state;
	if(!limit && dp[pos][u][v][state][n8][n4] != -1)
		return dp[pos][u][v][state][n8][n4];
	int up = limit ? num[pos]:9;
	for(int i =  0; i <= up; ++i){
		ans += dfs(pos-1, i, u, state || (i == u && i ==v), n8 ||(i == 8),n4 ||( i == 4), limit && (i == up));
	}
	if(!limit)	dp[pos][u][v][state][n8][n4] = ans;
	return ans;
}
ll solve(ll x){
	int len = 0;
	while(x){
		num[++len] = x % 10;
		x/=10;
	}
	if(len!=11)
		return 0;
	memset(dp, -1, sizeof(dp));
	ll ans = 0;
	for(int i = 1; i <= num[len]; ++i){
		ans += dfs(len-1,i,0,0,i==8,i==4,i == num[len]);
	}
	return ans;
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	ll a, b;
	cin >> a >> b;
	cout << solve(b) - solve(a-1);
	return 0;
}

posted @ 2023-09-14 17:47  龙鳞墨客  阅读(41)  评论(0)    收藏  举报