【題目來源】
https://www.acwing.com/problem/content/3713/
【題目描述】
給定兩個整數 l,r(l≤r),請問 [l, r] 範圍內,滿足數字的任意相鄰兩位差值都恰好為 1,且數字至少有兩位的數有多少個。
【輸入格式】
第一行包含整數 T,表示共有 T 組測試數據。
每組數據佔一行,包含兩個整數 l 和 r。
【輸出格式】
每組數據輸出一行,一個結果。
【輸入樣例】
14 2023
【輸出樣例】
1
17
【數據範圍】
1≤T≤100,
0≤l≤r≤3×10^8
【算法分析】
● 設 f[i][j] 表示長度為 i 最高位為 j 的滿足條件的數字個數。
● 由 for(int i=0; i<=9; i++) f[1][i]=1; 知,長度為 1 且分別以 0~9 開頭的一位數都滿足“任意相鄰兩位差值都恰好為 1”條件。這看起來似乎難以理解。這是因為,從邏輯上講,這個條件描述的是“任意兩個相鄰數字之差為1”。對於一位數來説,它只包含一個數字,不存在“相鄰數字”這個概念,因此條件中的前提“相鄰”並不成立。在數理邏輯中,一個前提為假的命題,其整體可以被視為真(這在邏輯學中稱為“空真”或“虛真”)。所以,在數位DP的初始化中,將一位數視為滿足條件是符合常規處理方式的。
● 計算所有長度為 i 最高位為 j 的“相鄰數字之差恰好為1”的數字個數。
for(int i=2; i<N; i++) {
for(int j=0; j<=9; j++) {
if(j==0) f[i][j]=f[i-1][1];
else if(j==9) f[i][j]=f[i-1][8];
else f[i][j]=f[i-1][j-1]+f[i-1][j+1];
}
}
此代碼具體含義如下:
當 j==0 時,f[i][j]=f[i-1][1],因為數字 0 後面只能接數字 1 才能滿足相鄰數字差為1的條件。
當 j==9 時,f[i][j]=f[i-1][8],因為數字 9 後面只能接數字 8 才能滿足條件。
對於其他數字 j(1 ~ 8),f[i][j]=f[i-1][j-1]+f[i-1][j+1],因為 j 後面可以接 j-1 或 j+1 來滿足條件。
● 在本題的數位 DP 解法代碼中,if(n<=0) return 0; 這一邊界條件確實至關重要,它確保了程序在處理非正數輸入時的正確性。
● 核心邏輯解析
(1)變量初始化
cnt:用於累計符合條件的數字個數。
pre:記錄上一位數字的值,初始化為 -1 表示尚未處理任何位。
(2)循環條件與位處理
循環 for(int i=v.size()-1; v.size()>=2 && i>=0; i--) 表示從最高位向最低位遍歷數字的每一位。
條件 v.size()>=2 表明此循環僅處理兩位數及以上的情況,一位數會通過其他邏輯單獨處理。
(3)當前位處理
代碼 int x=v[i]; 表示獲取當前位的實際值。
代碼 int start=(i==v.size()-1)?1:0; 表示確定當前位可以填的數字起始值。如果是最高位,不能為 0,所以 start=1;否則,start=0。
(4)統計當前位小於 x 的情況
for(int j=start; j<x; j++) {
if(pre==-1 || abs(pre-j)==1) {
cnt+=f[i+1][j];
}
}
遍歷當前位所有小於 x 的可能值 j。
如果 pre==-1(即當前是第一位),或者當前位 j 與前一位 pre 的差值為 1,則這是一個合法的數字開頭或延續。
f[i+1][j] 表示以 j 作為當前位,剩餘 i+1 位數字的所有合法組合數,並將其累加到 cnt 中。
(5)固定當前位為原數字值 x
if(abs(pre-x)==1 || pre==-1) pre=x;
else break;
檢查原數字在當前位的值 x 是否與前一位 pre 滿足差值條件(或 pre==-1 表示是第一位)。
如果滿足,則將 pre 更新為 x,繼續處理下一位。
如果不滿足,説明原數字 n 本身從這一位開始已經不滿足“相鄰數字差為1”的條件,後續所有位也必然不滿足,因此直接 break 退出循環。
(6)統計原數字 n 本身
if(i==0) cnt++;
當循環成功處理到最後一位(i==0)且沒有因不滿足條件而 break 時,説明原數字 n 本身也滿足條件,因此 cnt++。
(7)統計所有位數小於原數字 n 的合法數字個數
for(int i=2; i<=v.size()-1; i++) {
for(int j=1; j<=9; j++) cnt+=f[i][j];
}
遍歷所有可能的數字長度 i(從 2 到 v.size()-1)。
對於每個長度 i,遍歷所有可能的最高位 j(從 1 到 9,因為最高位不能為 0)。
累加 f[i][j] 的值到 cnt 中,f[i][j] 表示長度為 i 最高位為 j 的“相鄰數字之差恰好為 1”的數字個數。
【算法代碼】
#include <bits/stdc++.h>
using namespace std;
const int N=12;
int f[N][10]; //f[i][j]表示長度為i最高位為j的滿足條件的數字個數
void init() {
for(int i=0; i<=9; i++) f[1][i]=1;
for(int i=2; i<N; i++) {
for(int j=0; j<=9; j++) {
if(j==0) f[i][j]=f[i-1][1];
else if(j==9) f[i][j]=f[i-1][8];
else f[i][j]=f[i-1][j-1]+f[i-1][j+1];
}
}
}
int dp(int n) {
if(n<=0) return 0; //vip
vector<int> v;
while(n) {
v.push_back(n%10);
n/=10;
}
int cnt=0,pre=-1;
for(int i=v.size()-1; v.size()>=2 && i>=0; i--) {
int x=v[i];
int start=(i==v.size()-1)?1:0;
for(int j=start; j<x; j++) {
if(pre==-1 || abs(pre-j)==1) {
cnt+=f[i+1][j];
}
}
if(abs(pre-x)==1 || pre==-1) pre=x;
else break;
if(i==0) cnt++;
}
for(int i=2; i<=v.size()-1; i++) {
for(int j=1; j<=9; j++) cnt+=f[i][j];
}
return cnt;
}
int main() {
init();
int T,le,ri;
cin>>T;
while(T--) {
cin>>le>>ri;
cout<<dp(ri)-dp(le-1)<<endl;
}
}
/*
in:
4
1 10
10 10
1 100
0 300000000
out:
1
1
17
1992
*/