题目:给你一个字符串,判断他是这些字符组成的全排列中按字典序排序的第几位。
分析:组合数学。康托展开,枚举长度求和即可。
首先,考虑非重集,从前向后扫描,每一位找到前面相同本位小于对应值得串个数;
例如:f(cba)= 2*2!+ 1*1!= 5,对应第6个串;
(c**对应a、b开头的比他小,cb*对应ca开头的比他)
然后,考虑重集,对应位置取相同的元素相同,剩余的元素要去掉相同的全排列;
例如:f(cabaa)= (4!/3!+ 4!/2!)+ (0) + (2!/1!)+(0) = 18,对应第19个串;
(c****对应b开头为4!/3!(aaa)、a开头为4!/2!(aa),cab**对应caa开头为2!/1!(a))
注意,这里因为30!超过了long long,使用数组存所有的因数,利用gcd约分就得到int范围内的值了。
说明:还没有到600题╮(╯▽╰)╭。
#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
int gcd(int a, int b)
{
return a%b?gcd(b, a%b):b;
}
int main()
{
int letter[128];
int u[200],d[200];
char buf[32];
while (~scanf("%s",buf)) {
if (!strcmp("#", buf))
break;
memset(letter, 0, sizeof(letter));
for (int i = 0; buf[i]; ++ i)
letter[buf[i]] ++;
int len = strlen(buf),count = 1;
for (int i = 0; i < len; ++ i) {
int d_count = 0,u_count = 0;
for (int j = 'a'; j <= 'z'; ++ j)
for (int k = 2; k <= letter[j]; ++ k)
d[d_count ++] = k;
for (int k = 2; k <= len-1-i; ++ k)
u[u_count ++] = k;
for (int j = 'a'; j < buf[i]; ++ j) {
if (letter[j]) {
int g = 1,value = 1;
u[u_count] = letter[j];
for (int p = 0; p <= u_count; ++ p)
for (int q = 0; q < d_count; ++ q) {
g = gcd(u[p], d[q]);
u[p] /= g;
d[q] /= g;
}
for (int p = 0; p <= u_count; ++ p)
value *= u[p];
count += value;
}
}
letter[buf[i]] --;
}
printf("%10d\n",count);
}
return 0;
}