CCF 201312-4 有趣的数

问题描述
  我们把一个数称为有趣的,当且仅当:
  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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#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;
}

你可能感兴趣的:(ccf)