========================================= 原題 ============================================
1. 它的數字只包含0, 1, 2, 3,且這四個數字都出現過至少一次。
2. 所有的0都出現在所有的1之前,而所有的2都出現在所有的3之前。
3. 最高位數字不爲0。
因此,符合我們定義的最小的有趣的數是2013。除此以外,4位的有趣的數還有兩個:2031和2301。
請計算恰好有n位的有趣的數的個數。由於答案可能非常大,只需要輸出答案除以1000000007的餘數。
========================================= 思路 ============================================
聽聞網上大牛動不動就數位DP...寶寶做不到啊 =、=
看到這個題想了半天遞推公式,感覺不是很麻煩但就是不想推...然後就有了下面這種亂七八糟的解法:
首先看到題發現了兩個問題:
1、0/1和2/3之間沒有特殊聯繫
2、數字首位必須是2
先解釋下首位2的問題:如題0/2必須在1/3前面,0/1/2/3必須出現至少一次,所以首位只能是0/2(否則總有一個無法出現),但0不能做首位,所以首位必定是2。
然後就是最關鍵的一點——首個1和首個3的位置問題。
關於一個n位有趣數的可能情況如果直接寫可能非常複雜,即便按照遞推式也很麻煩,但只關注首個1/3就簡單多了:1出現的比3早、3出現的比1早。
假設首個1出現在第 i 位,首個3出現在第 j 位。
① 1出現的比3早(i < j)
由假設可知,在0~i 位之間沒有1/3只有0/2,在 i ~ j 之間只有1/2,在 j ~ n 之間只有1/3,出去固定位置(0、i、j)外每個位置都有兩種可能,共2^(n-3)種可能。其中存在不符合要求的可能性——全是2沒有0——i 之前沒有0,i之後不影響。所以減去錯誤可能2^(n-i-1),所得結果即爲當前i、j情況下結果總數。
② 3出現的比1早(i > j)
情況其實和前一種類似,只是考慮錯誤情況時候的計算變成了2^(n-i)種可能了(3在前面)。
知道了每種情況下的錯誤計算,還需要注意一下總數的問題:這裏總結果數取的是【1/3可能數 × 0/2可能數 - 錯誤數】,其中1/3可能因爲3的位置出現區別(3可以出現在第2位但1不可以,否則0無法出現)。
下面是AC代碼(附帶兩個防溢出的函數):
#define MOD 1000000007
#include<iostream>
using namespace std;
int getnum(int n){
int k = 1;
for (int i = 1; i <= n; i++){
k *= 2;
k %= MOD;
}
return k;
}
int mul(int a, int b){
int k = 0;
for (int i = 0; i < b; i++){
k += a;
k %= MOD;
}
return k;
}
int main(){
int n;
cin >> n;
long long n13 = ((n - 2)*(n - 3) + n - 2) % MOD;
long long n02 = getnum(n-3);
long long ans = (n13*n02) % MOD;
// 這裏直接把3出現的可能乘進去了
// 因爲2必定出現所以不用單獨考慮3的錯誤
// 首個1出現在首個3之前
for (int i = 3; i < n; i++){
if (n - i - 1 >= 0){
int wr = mul(getnum(n - i - 1), n - i);
ans -= wr;
// 分步取模可能出現計算後負數
while (ans <= 0){
ans += MOD;
}
}
}
// 首個3出現在首個1之前
for (int i = 3; i <= n; i++){
if (n - i >= 0){
int wr = mul(getnum(n - i), i - 2);
ans -= wr;
while (ans <= 0){
ans += MOD;
}
}
}
cout << ans%MOD << endl;
return 0;
}