问题描述
我们把一个数称为有趣的,当且仅当:
1. 它的数字只包含0, 1, 2, 3,且这四个数字都出现过至少一次。
2. 所有的0都出现在所有的1之前,而所有的2都出现在所有的3之前。
3. 最高位数字不为0。
因此,符合我们定义的最小的有趣的数是2013。除此以外,4位的有趣的数还有两个:2031和2301。
请计算恰好有n位的有趣的数的个数。由于答案可能非常大,只需要输出答案除以1000000007的余数。
输入格式
输入只有一行,包括恰好一个正整数n (4 ≤ n ≤ 1000)。
输出格式
输出只有一行,包括恰好n 位的整数中有趣的数的个数除以1000000007的余数。
样例输入
4
样例输出
3
我的方法:
这是一个排列组合的问题
根据题目限制,我们很容易就可以想到数字的第一位一定是2
假如说一共有i位数,我们接下来就要考虑剩下i-1位数了
剩下i-1位数中,至少有一个0,一个1,一个3
我们枚举01的个数,从2开始枚举,一直枚举到i-2
假如说i位数中,有j位是0或1,这就有C(j, i-1)中可能,也就是从i-1位数中挑出j位放0和1, 由于i<=1000,直接C(j, i-1)是求不出来的,我们就需要用到乘法逆元(详情见这里),j位数放0和1,由于0和1有前后顺序,所以只有j-1中可能,
剩下的i-j位放2和3,因为2已经放在第一位了,所以i-j位放2和3共有i-j-1种可能
所以共有 Σ (C(j,i-1) * (j-1) * (i-j-1))中可能
#include <iostream>
#include <iomanip>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <cmath>
#include <algorithm>
#define ll long long
#define N 1010
using namespace std;
ll ans[1003];
const ll mod = 1000000007;
ll extend_gcd(ll a, ll b, ll &x, ll &y) {
if (b) {
ll r = extend_gcd(b, a%b, y, x);
y -= x*(a/b);
return r;
} else {
x = 1;
y = 0;
return a;
}
}
int main()
{
ll i, j;
ans[0] = ans[1] = ans[2] = ans[3] = 0;
ans[4] = 3;
ll x, y, tmp;
for (i = 5; i <= 1000; i++) {
//i位数的情况
ans[i] = 0;
tmp = i-1;
for (j = 2; j < i-1; j++) {
//第一位一定是 2 所以只考虑后面i-1位
//j是0 1的出现个数和
extend_gcd(j, mod, x, y);
//计算j的乘法逆元
tmp = ((tmp*(i-j)%mod)*x%mod+mod)%mod;
// 计算C(j, i-1)
ans[i] += (j-1)*(i-j-1)*tmp;
// j个 位置分给0和1
// 0最少有1个,最多有j-1个
// 3最少有1个,最多有i-j-1个
ans[i] %= mod;
}
}
int n;
while(cin >> n) {
cout << ans[n] << endl;
}
return 0;
}
网上动态规划的方法
在不考虑条件1的情况下,共有六种合法状态:
0、只含2
1、只含2、0
2、只含2、3
3、只含2、0、1
4、只含2、0、3
5、含4种数字。
我们可以用dp[i][j],j = 0,1,…,5,来表示长度为i的整数,满足上述状态j的个数。
于是得到状态转移方程:
1、dp[i][0]=1,位数为i且只含2的整数有且只有1个
2、dp[i][1]=2*dp[i-1][1]+dp[i-1][0],位数为i且只含2、0的整数可以由位数为i-1的只含2、0的整数通过在末尾添加0或者2得到,也可以由位数为i-1的只含2的整数在末尾添加0得到。
3、dp[i][2]=dp[i-1][2]+dp[i-1][0],位数为i且只含2、3的整数可以由位数为i-1的只含2、3的整数通过在末尾添加3得到,也可以由位数为i-1的只含2的整数在末尾添加3得到。
其余的状态转移方程以此类推。
#include <iostream>
#include <iomanip>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <cmath>
#include <algorithm>
#define ll long long
#define N 1010
using namespace std;
const ll mod = 1000000007;
ll dp[1003][10];
int main()
{
// freopen("ans2.out", "w", stdout);
int n;
memset(dp, 0, sizeof(dp));
dp[1][0] = 1;
for (int i = 2; i <= 1000; i++) {
dp[i][0] = 1;//只含2
dp[i][1] = (dp[i - 1][1] * 2 % mod + dp[i - 1][0]) % mod;
//只含2、0 末尾0或2、末尾0
dp[i][2] = (dp[i - 1][0] + dp[i - 1][2]) % mod;
//只含2、3 末尾3
dp[i][3] = (dp[i - 1][1] + dp[i - 1][3] * 2 % mod) % mod;
//只含2、0、1 末尾2或1、末尾1
dp[i][4] = (dp[i - 1][1] + dp[i - 1][2] + dp[i - 1][4] * 2) % mod;
//只含2、0、3 末尾1或3、末尾0、末尾3
dp[i][5] = (dp[i - 1][3] + dp[i - 1][4] + dp[i - 1][5] * 2) % mod;
//含4个数字 末尾1或3
}
while(cin >> n) {
cout << dp[n][5] << endl;
}
return 0;
}