数位统计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;
}

浙公网安备 33010602011771号