基本的數位dp-windy數
首先讓我們來看一下數位DP這玩意到底是啥,對於一些剛開始練的萌新(我)來說會比較好。
數位DP一般他對問題的求解都是與數字上的位數有關,比如統計數字出現個數,有多少個數字之類的,這類問題一般能用數位dp做的,都是可以從小數字推到大數字的。比如我們從第一位往後可以一層一層,逐漸推出我們需要的答案。
先來一道簡單點的例題
windy定義了一種windy數。不含前導零且相鄰兩個數字之差至少爲2的正整數被稱爲windy數。 windy想知道,
在A和B之間,包括A和B,總共有多少個windy數?
設表示x前面的數 即 中windy數的個數(注意這是開區間)
那麼顯然如果要求 中windy數的個數就是:
數位dp開始,預處理位數爲最高位爲的windy數個數
轉移:
:其中是非負整數且
初始值:
| 其中爲非負整數
求f(x)怎麼求呢?
爲了方便處理先對數進行按10進制位拆分到數組,然後我們分三種情況進行討論(代碼中有詳細註釋)
- 顯然位數比x要小的數字都是合法的都在區間內,直接統計就行
- 位數和x一樣最高位的數字比x小的數字都是合法的都在區間內直接統計就行
- 位數和x一樣,最高位又和x一樣我們從左到右掃一遍x各個位子上的數字大小然後枚舉合法的該位子上的數判斷是否合法就行。
其實每個數位dp基本上都是這個思路,只不過相鄰位數之間要滿足的條件不同,有幾個易錯點需要注意
- 討論最高位時一定不要將0加進去,如上面的1,2兩種情況,一個數的最高位只能從1-9。第三種情況最高位確定了,討論中間每一位時才能加上0,
- 注意我們求得是開區間的滿足條件的數的總數。
#define inf 0x3f3f3f3f
#define MAX 1000005
#define ll long long
#define vec vector<ll>
#define P pair<int,int>
ll dp[15][10];//dp[i][j]:位數爲i,最高位爲j時最多有多少windy數
//統計[1,x)區間內的windy數
ll cal(ll x) {
ll a[11], len = 0;
while (x > 0) {
a[++len] = x % 10;
x /= 10;
}
ll sum = 0;
//1:統計位數比x少的
for (int i = 1; i < len; i++)
for (int j = 1; j < 10; j++)//注意這裏從1開始,最高位不能爲0!
sum += dp[i][j];
//2:統計位數和x一樣,但是首位比x小的,同樣最高位不能爲0
for (int i = 1; i < a[len]; i++)
sum += dp[len][i];
//3:統計位數與x一樣,首位也一樣的,那麼之前的每一位加上限制a[i]
for (int i = len - 1; i >= 1; i--) {
for (int j = 0; j < a[i]; j++) //注意該位的限制,由於不是最高位,可以從0開始
if (abs(j - a[i + 1]) >= 2)//該位和上一位滿足要求
sum += dp[i][j];
if (abs(a[i + 1] - a[i]) < 2)//傳入數字連續兩位不滿足了
break;
}
return sum;
}
int main() {
memset(dp, 0, sizeof(dp));
for (int i = 0; i < 10; i++)dp[1][i] = 1;//預處理第一位
for (int i = 2; i < 11; i++) //遍歷每2-10每個位數,1預處理了
for (int j = 0; j < 10; j++) //遍歷這一位的每種可能數字
for (int k = 0; k < 10; k++) //遍歷上一位的每種可能數字
if (abs(k - j) >= 2) //相鄰數字之差大於2
dp[i][j] += dp[i - 1][k];
ll a, b;
while (cin >> a >> b) {
if (a > b)swap(a, b);
cout << cal(b + 1) - cal(a) << endl;
}
}
文章主要參考自洛谷的兩位大佬的題解
https://www.luogu.com.cn/problem/solution/P2657
https://www.luogu.com.cn/blog/mak2333/swdp001
略微複雜的數位dp-數字計數
這道題相比於windy數,難度會稍微大一點,但也是數位dp的應用,數位dp其實就是兩個部分。初始化、求解。
設表示第位,最高位爲,有多少個。
這裏用到了前綴和的思想。比如就表示有多少個。
在求解的時候,我們就用算出小於的數對應的每位數出現的,然後再求出。
首先我們可以得到一個粗略的方程:
這一個很玄學的數是指在這一位上出現的次數。
那麼在這一位上出現了多少次呢?
當的時候,比如最高位爲1,四位數字1xxx,求1出現的次數,那麼還要加上,即1在最高位出現的次數,
這個求解又被一些大佬稱爲“拆數”,可以遞歸和非遞歸實現,我比較喜歡非遞歸
求解過程一般有以下幾步:
- 首先把原數拆了,存入數組;
- 統計位數比原數少,不含前導零的數字;
- 統計位數與原數一樣,但最高位較小的數,同樣不能有前導零;
- 位數和x一樣,最高位又和x一樣我們從左到右掃一遍x各個位子上的數字大小然後枚舉合法的該位子上的數,值得注意的是,比如我們有個四位數,第二位,第三位,第四位還給數A帶來了的增加量,同樣有的增加量。
#define inf 0x3f3f3f3f
#define MAX 1000005
#define ll long long
#define vec vector<ll>
#define P pair<int,int>
//dp[i][j][k]:i位數,最高位爲j,小於他的數字中k出現的次數
ll dp[20][10][10];
vec cnt(ll x) {
ll a[20], len = 0;
vec v(10);
while (x > 0) {
a[++len] = x % 10, x /= 10;
}
//位數比x少,忽略前導0
for (int i = 1; i < len; i++)
for (int j = 1; j < 10; j++)
for (int k = 0; k < 10; k++)
v[k] += dp[i][j][k];
//首位比x小,忽略前導0
for (int i = 1; i < a[len]; i++)
for (int k = 0; k < 10; k++)
v[k] += dp[len][i][k];
//首位一樣,按位退回,每位設限
for (int i = len - 1; i >= 1; i--) {
for (int j = 0; j < a[i]; j++) {
for (int k = 0; k < 10; k++) {
v[k] += dp[i][j][k];
}
}
//6153 6後面的每一位分別爲他帶來了 1*100+5*10+3的包含6的個數(以6爲千位)
for (int j = len; j > i; j--) {
v[a[j]] += a[i] * pow(10, i - 1);
}
}
return v;
}
int main() {
memset(dp, 0, sizeof(dp));
for (int i = 0; i < 10; i++) dp[1][i][i] = 1;//只有該位爲i時,該值才爲1
for (int i = 2; i < 20; i++) //位數
for (int j = 0; j < 10; j++) {//這一位
for (int k = 0; k < 10; k++) {//上一位
for (int n = 0; n < 10; n++) //元素
dp[i][j][n] += dp[i - 1][k][n];
}
dp[i][j][j] += pow(10, i - 1);//加上這個特殊值
}
ll a, b;
while (cin >> a >> b) {
vec v1 = cnt(b + 1);
vec v2 = cnt(a);
for (int i = 0; i < 10; i++)
cout << v1[i] - v2[i] << ' ';
cout << endl;
}
}