CCF 201312-4 有趣的数 (数位DP, 状压DP, 组合数学+暴力枚举, 推公式, 矩阵快速幂)

时间:2024-07-02 19:06:02
问题描述
  我们把一个数称为有趣的,当且仅当:
  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
析:先说第一种方法,数位DP:
首先只有0123,并且有约束条件,那么就只有6种状态,{2},{20},{201},{23},{203},{2031},然后依次对每一位进行计算即可。
第二种,状压DP:
因为只有4种数字,所以可能用状压DP,1表示0, 2表示1, 4表示2,8表示3.剩下的就简单了。
第三种,组合数学+暴力枚举:
先枚举01的数量,那么最少2个,最多n-2个,由于0是不能放在首位,所以就有c(n-1, i),i 表示有多少个01,然后再考虑有多种方法,
应该有 n-i 种方法,因为0的数量是从1个到 i-1 个,同理,23就有 n-i-1 种,然后乘起来就好。
第四种,推公式:
这个方法和第三种一样的,只不过是把第三种简化了,然后成一个递推公式,an=(n*n-5n+4)*2^(n-3)+n-1。
第五种,矩阵快速幂:
对每一个组成数字进行分析,字符串必定以2开头,把字符串中字符放入一个集合,我们可以统计长度为N,字符集合为S的合法字符串个数,
然后把它们的关系表示成一个矩阵,就OK了。
代码如下:
数位DP:
#pragma comment(linker, "/STACK:1024000000,1024000000")
#include <cstdio>
#include <string>
#include <cstdlib>
#include <cmath>
#include <iostream>
#include <cstring>
#include <set>
#include <queue>
#include <algorithm>
#include <vector>
#include <map>
#include <cctype>
#include <stack>
#define freopenr freopen("in.txt", "r", stdin)
#define freopenw freopen("out.txt", "w", stdout)
using namespace std; typedef long long LL;
typedef pair<int, int> P;
const int INF = 0x3f3f3f3f;
const double inf = 0x3f3f3f3f3f3f;
const LL LNF = 100000000000000000;
const double PI = acos(-1.0);
const double eps = 1e-8;
const int maxn = 1e3 + 5;
const int mod = 1e9 + 7;
const char *mark = "+-*";
const int dr[] = {-1, 0, 1, 0};
const int dc[] = {0, 1, 0, -1};
int n, m;
inline bool is_in(int r, int c){
return r >= 0 && r < n && c >= 0 && c < m;
}
inline LL Max(LL a, LL b){ return a < b ? b : a; }
inline LL Min(LL a, LL b){ return a > b ? b : a; }
inline int Max(int a, int b){ return a < b ? b : a; }
inline int Min(int a, int b){ return a > b ? b : a; }
LL dp[maxn][5]; int main(){
while(cin >> n){
dp[4][0] = 7; dp[4][1] = 5; dp[4][2] = 3; dp[4][3] = 9; dp[4][4] = 3;
for(int i = 5; i <= n; ++i){
dp[i][0] = (1 + 2 * dp[i-1][0]) % mod;
dp[i][1] = (dp[i-1][0] + 2 * dp[i-1][1]) % mod;
dp[i][2] = (1 + dp[i-1][2]) % mod;
dp[i][3] = (dp[i-1][0] + dp[i-1][2] + 2 * dp[i-1][3]) % mod;
dp[i][4] = (dp[i-1][1] + dp[i-1][3] + 2 * dp[i-1][4]) % mod;
}
cout << dp[n][4] << endl;
}
}

状压DP:

#pragma comment(linker, "/STACK:1024000000,1024000000")
#include <cstdio>
#include <string>
#include <cstdlib>
#include <cmath>
#include <iostream>
#include <cstring>
#include <set>
#include <queue>
#include <algorithm>
#include <vector>
#include <map>
#include <cctype>
#include <cmath>
#include <stack>
#define frer freopen("in.txt", "r", stdin)
#define frew freopen("out.txt", "w", stdout)
using namespace std; typedef long long LL;
typedef pair<int, int> P;
const int INF = 0x3f3f3f3f;
const double inf = 0x3f3f3f3f3f3f;
const double PI = acos(-1.0);
const double eps = 1e-8;
const int maxn = 1e3 + 5;
const int mod = 1e9 + 7;
const int dr[] = {-1, 1, 0, 0};
const int dc[] = {0, 0, 1, -1};
const char *de[] = {"0000", "0001", "0010", "0011", "0100", "0101", "0110", "0111", "1000", "1001", "1010", "1011", "1100", "1101", "1110", "1111"};
int n, m;
const int mon[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
const int monn[] = {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
inline int Min(int a, int b){ return a < b ? a : b; }
inline int Max(int a, int b){ return a > b ? a : b; }
inline LL Min(LL a, LL b){ return a < b ? a : b; }
inline LL Max(LL a, LL b){ return a > b ? a : b; }
inline bool is_in(int r, int c){
return r >= 0 && r < n && c >= 0 && c < m;
}
LL dp[maxn][(1<<4)+1]; int main(){
while(cin >> n){
memset(dp, 0, sizeof dp);
dp[0][0] = 1;
for(int i = 0; i < n; ++i){
for(int j = 0; j < 16; ++j){
if(!dp[i][j]) continue;
if(!(j & 2) && i) dp[i+1][j|1] = (dp[i+1][j|1] + dp[i][j]) % mod;
dp[i+1][j|2] = (dp[i+1][j|2] + dp[i][j]) % mod;
if(!(j & 8)) dp[i+1][j|4] = (dp[i+1][j|4] + dp[i][j]) % mod;
dp[i+1][j|8] = (dp[i+1][j|8] + dp[i][j]) % mod;
}
}
cout << dp[n][15] << endl;
}
return 0;
}

组合数学+暴力枚举:

#pragma comment(linker, "/STACK:1024000000,1024000000")
#include <cstdio>
#include <string>
#include <cstdlib>
#include <cmath>
#include <iostream>
#include <cstring>
#include <set>
#include <queue>
#include <algorithm>
#include <vector>
#include <map>
#include <cctype>
#include <cmath>
#include <stack>
#define frer freopen("in.txt", "r", stdin)
#define frew freopen("out.txt", "w", stdout)
using namespace std; typedef long long LL;
typedef pair<int, int> P;
const int INF = 0x3f3f3f3f;
const double inf = 0x3f3f3f3f3f3f;
const double PI = acos(-1.0);
const double eps = 1e-8;
const int maxn = 1e3 + 5;
const int mod = 1e9 + 7;
const int dr[] = {-1, 1, 0, 0};
const int dc[] = {0, 0, 1, -1};
const char *de[] = {"0000", "0001", "0010", "0011", "0100", "0101", "0110", "0111", "1000", "1001", "1010", "1011", "1100", "1101", "1110", "1111"};
int n, m;
const int mon[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
const int monn[] = {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
inline int Min(int a, int b){ return a < b ? a : b; }
inline int Max(int a, int b){ return a > b ? a : b; }
inline LL Min(LL a, LL b){ return a < b ? a : b; }
inline LL Max(LL a, LL b){ return a > b ? a : b; }
inline bool is_in(int r, int c){
return r >= 0 && r < n && c >= 0 && c < m;
}
LL c[maxn][maxn];
void init(){
for(int i = 0; i < n; ++i) c[i][0] = c[i][i] = 1;
for(int i = 2; i < n; ++i)
for(int j = 1; j < i; ++j)
c[i][j] = (c[i-1][j] + c[i-1][j-1]) % mod;
} int main(){
while(cin >> n){
init();
LL ans = 0;
for(int i = 2; i < n-1; ++i)
ans = (ans + (c[n-1][i] * (i-1) * (n-i-1)) % mod) % mod;
cout << ans << endl;
}
return 0;
}

推公式:

#pragma comment(linker, "/STACK:1024000000,1024000000")
#include <cstdio>
#include <string>
#include <cstdlib>
#include <cmath>
#include <iostream>
#include <cstring>
#include <set>
#include <queue>
#include <algorithm>
#include <vector>
#include <map>
#include <cctype>
#include <cmath>
#include <stack>
#define frer freopen("in.txt", "r", stdin)
#define frew freopen("out.txt", "w", stdout)
using namespace std; typedef long long LL;
typedef pair<int, int> P;
const int INF = 0x3f3f3f3f;
const double inf = 0x3f3f3f3f3f3f;
const double PI = acos(-1.0);
const double eps = 1e-8;
const int maxn = 1e3 + 5;
const int mod = 1e9 + 7;
const int dr[] = {-1, 1, 0, 0};
const int dc[] = {0, 0, 1, -1};
const char *de[] = {"0000", "0001", "0010", "0011", "0100", "0101", "0110", "0111", "1000", "1001", "1010", "1011", "1100", "1101", "1110", "1111"};
int n, m;
const int mon[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
const int monn[] = {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
inline int Min(int a, int b){ return a < b ? a : b; }
inline int Max(int a, int b){ return a > b ? a : b; }
inline LL Min(LL a, LL b){ return a < b ? a : b; }
inline LL Max(LL a, LL b){ return a > b ? a : b; }
inline bool is_in(int r, int c){
return r >= 0 && r < n && c >= 0 && c < m;
}
LL quick_pow(LL a, LL b){
LL ans = 1;
while(b){
if(b & 1) ans = (ans * a) % mod;
b >>= 1;
a = (a * a) % mod;
}
return ans;
} int main(){
while(cin >> n){
LL ans = (n * n - 5 * n + 4) % mod;
ans = (ans * quick_pow(2LL, n-3) + n - 1) % mod;
cout << ans << endl;
}
return 0;
}

矩阵快速幂:

#pragma comment(linker, "/STACK:1024000000,1024000000")
#include <cstdio>
#include <string>
#include <cstdlib>
#include <cmath>
#include <iostream>
#include <cstring>
#include <set>
#include <queue>
#include <algorithm>
#include <vector>
#include <map>
#include <cctype>
#include <cmath>
#include <stack>
#define frer freopen("in.txt", "r", stdin)
#define frew freopen("out.txt", "w", stdout)
using namespace std; typedef long long LL;
typedef pair<int, int> P;
const int INF = 0x3f3f3f3f;
const double inf = 0x3f3f3f3f3f3f;
const double PI = acos(-1.0);
const double eps = 1e-8;
const int maxn = 1e3 + 5;
const int mod = 1e9 + 7;
const int dr[] = {-1, 1, 0, 0};
const int dc[] = {0, 0, 1, -1};
const char *de[] = {"0000", "0001", "0010", "0011", "0100", "0101", "0110", "0111", "1000", "1001", "1010", "1011", "1100", "1101", "1110", "1111"};
int n, m;
const int mon[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
const int monn[] = {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
inline int Min(int a, int b){ return a < b ? a : b; }
inline int Max(int a, int b){ return a > b ? a : b; }
inline LL Min(LL a, LL b){ return a < b ? a : b; }
inline LL Max(LL a, LL b){ return a > b ? a : b; }
inline bool is_in(int r, int c){
return r >= 0 && r < n && c >= 0 && c < m;
}
struct Matrix{
LL a[6][6];
}; Matrix mul(Matrix x, Matrix y){
Matrix ans;
memset(ans.a, 0, sizeof ans.a);
for(int i = 0; i < 6; ++i){
for(int j = 0; j < 6; ++j){
for(int k = 0; k < 6; ++k){
ans.a[i][j] += (x.a[i][k] * y.a[k][j]) % mod;
}
ans.a[i][j] %= mod;
}
}
return ans;
} Matrix quick_pow(Matrix x, int b){
Matrix ans;
memset(ans.a, 0, sizeof ans.a);
for(int i = 0; i < 6; ++i)
ans.a[i][i] = 1;
while(b){
if(b & 1) ans = mul(ans, x);
b >>= 1;
x = mul(x, x);
}
return ans;
} int main(){
Matrix x, y;
memset(x.a, 0, sizeof x.a);
memset(y.a, 0, sizeof y.a);
x.a[3][3] = x.a[0][0] = x.a[1][0] = x.a[2][1] = x.a[3][0] = x.a[4][1] = x.a[4][3] = x.a[5][2] = x.a[5][4] = x.a[5][5] = 1;
x.a[1][1] = x.a[4][4] = x.a[5][5] = x.a[2][2] = 2;
y.a[0][0] = 1;
while(cin >> n){
Matrix ans = mul(quick_pow(x, n-1), y);
cout << ans.a[5][0] << endl;
}
return 0;
}