题意:
给一个字符串str,然后求出以其中字母为全排列的字符串里,按字典序排序的第pos个回文串。
解析:
cf有遇到一个康拓展开,但是不会。
这题用编码解码,用小白上的公式:设字符共有k类,则当前种数:cnt = (n1 + n2 + n3 +... + nk) / (n1! * n2! * n3! * ...*nk!)
然后遍历每一个个数,一点一点去逼近。
无解的情况是字符串出现的奇数个字符多与一个以及全部的展开达不到。
错误点是c[i]--必须要提前执行。
代码:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <stack>
#include <vector>
#include <queue>
#include <map>
#include <climits>
#include <cassert>
#define LL long long
using namespace std;
const int maxn = 1e6;
const int inf = 0x3f3f3f3f;
const double eps = 1e-8;
const double pi = 4 * atan(1.0);
const double ee = exp(1.0);
char str[30 + 10];
int c[30];//abcdefg...
char midc;//mid char
int ans[30 + 10];
LL pos, len;
LL cantor[16];
void cantor_table()
{
cantor[1] = 1;
cantor[0] = 1;
for (int i = 2; i < 16; i++)
cantor[i] = cantor[i - 1] * i;
}
bool has_pal()
{
midc = '\0';
memset(c, 0, sizeof(c));
len = strlen(str);
for (int i = 0; i < len; i++)
{
c[str[i] - 'a']++;
}
int cnt = 0;
for (int i = 0; i < 26; i++)
{
if (c[i] & 1)
{
cnt++;
midc = i + 'a';
}
c[i] >>= 1;
if (1 < cnt)
{
return false;
}
}
len >>= 1;
return true;
}
void solve()
{
LL fenzi = 1, fenmu = 1;
fenzi = cantor[len];
for (int i = 0; i < 26; i++)
fenmu *= cantor[c[i]];
if (fenzi / fenmu < pos)
{
printf("XXX\n");
return;
}
memset(ans, 0, sizeof(ans));
for (int cur = 1; cur <= len; cur++)
{
for (int i = 0; i < 26; i++)
{
if (c[i])
{
c[i]--;
fenzi = cantor[len - cur];
fenmu = 1;
for (int j = 0; j < 26; j++)
if (c[j])
fenmu *= cantor[c[j]];
LL t = fenzi / fenmu;
if (t < pos)
{
c[i]++;
pos -= t;
}
else
{
//c[i]--; bug
ans[cur] = i;
break;
}
}
}
}
for (int i = 1; i <= len; i++)
printf("%c", ans[i] + 'a');
if (midc)
printf("%c", midc);
for (int i = len; 1 <= i; i--)
printf("%c", ans[i] + 'a');
printf("\n");
}
int main()
{
#ifdef LOCAL
freopen("in.txt", "r", stdin);
#endif // LOCAL
cantor_table();
int ncase;
int ca = 1;
scanf("%d", &ncase);
while (ncase--)
{
scanf("%s%lld", str, &pos);
printf("Case %d: ", ca++);
if (!has_pal())
{
printf("XXX\n");
}
else
{
solve();
}
}
return 0;
}
详细过程,转自:http://www.cnblogs.com/scau20110726/archive/2013/02/04/2891543.html
一个回文串如果长度为偶数,那么可以确定,每种字符的个数一个是偶数,不会有字符的个数为奇数。如果一个回文串为奇数,那么可以确定,一定有且仅有一种字符的个数为奇数,其他字符的个数都会偶数,而且整个回文串中间的那个字符必定是为奇数的那个字符。所以对于长度为奇数的回文串,我们可以暂时除掉中间的那个字符(因为它是固定一定要在那里,没什么研究价值),把它变为一个长度为偶数的回文串(但是记得最终输出的时候把除掉的那个字符补上)。我们来看长度为偶数的回文串,两两对称,所有我们只需要研究左半部分,右半部分只是复制罢了,也就是说,一个回文串最终长什么样,是由左半部分决定的。
所以我们只对左半部分按照字典序排序,那么左半部分的排序结果也会是整个回文串的排序结果
有人不禁要质疑上面的这条结论,说是会不会左半部分的排序和整个回文串的排序不同,这是不可能的,因为字典序的比较是第一次遇到不同就停止。
所以我们要找第n个回文串,实际就是找左半字符串全排列中的第n个排列,这是一个解码的过程,就是一位一位地确定字符
我们用例子来说明问题:
假设经过处理后我们的到的左半部分字符串是ababcdac,我们要知道第698个排列的字符串
3个a,2个b,2个c,1个d,总长度为8
我们从第一位开始确定,第一位可以是a,b,c,d
第一位为a,剩下2个a,2个b,2个c,1个d,他们的全排列个数要用组合数学来算为 (2+2+2+1)! / (2!2!2!1!)=630
而698>630,所以第1位不能填a , 698-630=68
第一位为b,剩下3个a,1个b,2个c,1个d,全排序个数为(3+1+2+1)! / (3!1!2!1!)=420
而68<420 , 说明第一位填b
——————————————————————————
68 , 3个a,1个b,2个c,1个d,总长度为7
若第二位填a,剩下2个a,1个b,2个c,1个d,全排列数为 6!/ 2!1!2!1! = 180 , 68<180 , 说明第2位填a
——————————————————————————
68 , 2个a,1个b,2个c,1个d , 总长度为6
若第3位填a,剩下1个a,1个b,2个c,1个d, 全排列数为 5!/ 1!1!2!1! = 60 , 68>60 , 不能填a , 68-60=8
若第3位填b,剩下2个a,2个c,1个d,全排列为 5!/ 2!2!1! = 30 , 8<30 , 说明第3位填b
———————————————————————————
8 , 2个a,2个c,1个d , 总长度为5
若第4位填a,剩下1个a,2个c,1个d , 全排列个数为 4!/ 1!2!1! = 12 , 若8<12 , 说明第4位填a
———————————————————————————
8,1个a,2个c,1个d , 总长度为4
若第5位填a,剩下2个c,1个d,全排列数为 3!/ 2!1!=3 , 8>3 , 不能填a,8-3=5
若第5位填c,剩下1个a,1个c,1个d,全排列为 3! / 1!1!1! = 6 , 5<6 , 说明第5位填c
————————————————————————————
5 , 1个a,1个c,1个d , 总长度为3
若第6位为a , 剩下 1个c,1个d , 全排列为 2 , 5>2 , 不能填a , 5-2=3
若第6位为c, 剩下1个a,1个d, 全排列为 2 , 3>2 , 不能填c , 3-2=1
若第6位为d, 剩下1个a,1个c,全排列为2 , 1<2 , 说明第6位为d
—————————————————————————————
1 , 1个a,1个c , 总长度为2
若第7位为a , 剩下1个c, 全排列数为 1 , 1<=1 , 说明第7位填a
——————————————————————————————
1 , 1个c , 第8位填c
——————————————————————————————
最终得到字符串为 babacdac
上面的迭代过程用代码实现即可,不过注意一个陷阱,输入中的字符串可能本身根本无法构成回文(超过一种字符的个数为奇数)