题意:
题意:给定一个k个数字,求由 [1,k] 的数字组成的,第n个全排列,由于n很大,输入的方式为 ∑ki=1Si∗(K−i)!
解析:
一开始把题目理解错了,我还以为是给你 {S1...Sn} ,叫你算出符合 {S1...Sn} 的全排列的第n个,且满足 n=∑ki=1Si∗(K−i)!
原来正确的题意:是给你 Sn 叫你先算出 n ,再算出符合条件的由[1,k] 的数字组成的,第n个全排列。
那么先算出n再求出全排列那是不可能的,因为n实在是太大了。
其实题目所给出的这个公式其实就是康拓展开的公式,不懂的可以百度百科一下。
由观察得知 (k−i)! 就是 k−i 个数字的全排列种数, 0=<Si<=k−i ,所以显然可知假设当 i=1 时,从第 (k−1)!∗S1 到第n个全排列都是由第 S1+1 个数字开始的数列,因为每 (k−1)! 次排列过后,下一个排列的第1个数字都要增大1。举个例子:
比如我1 2 3 4 5 的全排列
如果以1打头的种数有 4! 种,那我现在要第k大的,假设 k=3 ,那么现在全排列至少是第 (2∗4!+1) 大的,因为1打头有 4! 个, 2打头的有 4! 个,然后你就这样一位一位去确定 现在要的是第多少大的, 并且这个数字是多少。那么现在问题可以转化为:起初K个人排队,1,2,3,…,K,每次给定一个S,找到当前排第S位的人,让其出列,再如此循环,要求每次都准确找到排第S位的人是哪一个人。注意每一次S都可能不一样,出列后的人不再属于当前队伍。怎么做?用线段树动态查找就可以实现。
my code
#include <cstdio>
#include <cstring>
#define ls (o<<1)
#define rs (o<<1|1)
#define lson ls,L,M
#define rson rs,M+1,R
using namespace std;
const int N = 50005;
int sumv[N<<2];
void pushUp(int o) {
sumv[o] = sumv[ls] + sumv[rs];
}
void build(int o, int L, int R) {
sumv[o] = 0;
if(L == R) {
sumv[o] = 1;
return ;
}
int M = (L+R)/2;
build(lson);
build(rson);
pushUp(o);
}
int val;
void modify(int o, int L, int R) {
if(L == R) {
sumv[o]--;
printf("%d", L);
return ;
}
int M = (L+R)/2;
if(sumv[ls] >= val) {
modify(lson);
}else {
val -= sumv[ls];
modify(rson);
}
pushUp(o);
}
int n;
int main() {
int T;
scanf("%d", &T);
while(T--) {
scanf("%d", &n);
build(1, 1, n);
for(int i = 1; i <= n; i++) {
scanf("%d", &val);
val++;
modify(1, 1, n);
if(i < n) putchar(' ');
}puts("");
}
return 0;
}