[Swust OJ 715]--字典序问题(组合数预处理/数位dp)
题目链接:http://acm.swust.edu.cn/problem/715/
Time limit(ms): 1000 Memory limit(kb): 65535
在数据加密和数据压缩中常需要对特殊的字符串进行编码。给定的字母表A 由26 个小写英文字母组成A={a,b,…,z}。该字母表产生的升序字符串是指字符串中字母按照从左到右出现的次序与字母在字母表中出现的次序相同,且每个字符最多出现1 次。例如,a,b,ab,bc,xyz 等字符串都是升序字符串。现在对字母表A 产生的所有长度不超过6 的升序字符串按照字典序排列并编码如下。
对于给定的长度不超过6 的升序字符串,编程计算出它在上述字典中的编码。
1 2 … 26 27 28 …
a b … z ab ac …
对于给定的长度不超过6 的升序字符串,编程计算出它在上述字典中的编码。
Description
文件的第一行是一个正整数k,表示接下来共有k 行。
接下来的k行中,每行给出一个字符串。
接下来的k行中,每行给出一个字符串。
Input
共有k 行,每行对应于一个字符串的编码。
Output
1
2
3
|
2
a
b
|
Sample Input
1
2
|
1
2
|
Sample Output
解题思路:这道题用数位dp的话不太现实(状态设计太诡异的说~~~)
那么指定个 字母 只有一个排列合法,符合组合数的概念,可以考虑使用组合数
然后在当前状态下,求长度小于len的总个数,等于len的当前序列的总个数(相当于把问题细化了)
具体的看代码吧~~~
值得注意的是 利用杨辉三角计算组合数 且合数性质cur[i][j]=cur[i][i-j];
代码如下:
1 #include <iostream> 2 #include <cstring> 3 using namespace std; 4 5 char s[7]; 6 int cur[27][27] = { 1 }; 7 8 //预处理 利用杨辉三角计算组合数 9 void init(){ 10 int i, left, right; 11 for (i = 1; i <= 26; i++){ 12 cur[i][0] = cur[i][i] = 1; 13 left = 1, right = i - 1; 14 while (left <= right){ 15 cur[i][left] = cur[i - 1][left - 1] + cur[i - 1][left]; 16 cur[i][right--] = cur[i][left++];//组合数性质cur[i][j]=cur[i][i-j]; 17 } 18 } 19 } 20 21 //长度小于len的串的个数 22 int minlen_num(int len){ 23 int i, cnt = 0; 24 for (i = 1; i < len; i++) 25 cnt += cur[26][i]; 26 return cnt; 27 } 28 29 //当前长度下当前串前面的个数 30 int enquallen_num(int len){ 31 int i, j, cnt = 0, pre = -1, tmp; 32 for (i = 0; i < len; i++){ 33 tmp = s[i] - 'a'; 34 for (j = pre + 1; j < tmp; j++) 35 cnt += cur[26 - j - 1][len - i - 1]; 36 pre = tmp; 37 } 38 return cnt; 39 } 40 41 bool judge(int len){ 42 for (int i = 1; i < len; i++) 43 if (s[i] <= s[i - 1]) 44 return false; 45 return true; 46 } 47 48 int main(){ 49 init(); 50 int t, cnt, len; 51 cin >> t; 52 while (t--){ 53 cnt = 0; 54 cin >> s; 55 len = strlen(s); 56 if (!judge(len)) cout << 0 << endl; 57 else{ 58 cnt += minlen_num(len); 59 cnt += enquallen_num(len); 60 cout << cnt + 1 << endl; 61 } 62 } 63 return 0; 64 }
6/18号我又回来了,这道题是可以数位dp的,当时我设计状态的时候果断逗比了~~~直接在递归时,加个判断就限制了后面数字的选取状态就可以强势ac了
(感谢学长的指点)
代码如下:
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 using namespace std; 5 #define ll long long 6 #define N 110 7 8 char s[N]; 9 ll bit[N]; 10 ll dp[N][N]; 11 12 ll dfs(ll pos, ll mx, bool limit, bool fzero) 13 { 14 if (pos == -1) return 1; 15 if (!limit && !fzero && ~dp[pos][mx]) return dp[pos][mx]; 16 ll end = limit ? bit[pos] : 26; 17 ll ans = 0; 18 for (ll i = fzero ? 0 : mx; i <= end; i++)//这里就判断了后面的字母的选取是否有限制(题目上的升序) 19 { 20 ans += dfs(pos - 1, i + 1, limit && i == end, fzero && !i); 21 } 22 if (!limit && !fzero) dp[pos][mx] = ans; 23 return ans; 24 } 25 ll cal() 26 { 27 ll len = strlen(s + 1); 28 for (ll i = 1; i <= len; i++) 29 { 30 bit[len - i] = s[i] - 'a' + 1; 31 } 32 return dfs(len - 1, 0, 1, 1); 33 } 34 int main() 35 { 36 int t; 37 cin >> t; 38 while (t--) 39 { 40 memset(dp, -1, sizeof(dp)); 41 scanf("%s", s + 1); 42 int flag = 1; 43 int len = strlen(s + 1); 44 for (int i = 1; i<len; i++) 45 { 46 if (!(s[i]<s[i + 1])) 47 { 48 flag = 0; 49 break; 50 } 51 } 52 if (!flag) printf("0\n"); 53 else 54 printf("%lld\n", cal() - 1); 55 } 56 return 0; 57 }
如果这是你所爱的,就不要让自己后悔~~~