题意:给定整数n和k,输出1-k的所有排列中,按照字典序从小到大排序后的第n个(编号从0开始)。由于n特别大,用另一种方式给出n,n=S1*(k-1)!+S2*(k-2)!+..+Sk-1 *1!+Sk*0!
思路:假设k=5,S数列是4 2 0 1 0,把n分解成上述式子相加,第一项是4*4!,不难发现这项可以确定了要求的排列中的第一项就是5,因为从12345到21345的变化其实就是4!(注意题意编号是从0开始),同理第二项是2*3!,就从剩下的1 2 3 4中确定了要求排列中第二项是3,以此类推,答案是5 3 1 4 2,然后如何快速求出来这个区间第Si+1小呢,带更新的主席树?其实这题线段树就可解决,因为我们是从一个有序区间求第Si+1小的数,设区间的值为这个区间有几个数就可以了,如果Lson>=Si+1,下次查询Lson的Si+1,否则下次查询Rson的Si+1-Lson。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=2*1e5;
int sum[maxn],k,ans[maxn],tot;
void build(int l,int r,int o)
{
if(l==r)
{
sum[o]=1;
return;
}
int ls=o*2,rs=o*2+1;
int m=(l+r)/2;
build(l,m,ls);
build(m+1,r,rs);
sum[o]=sum[ls]+sum[rs];
}
void query(int l,int r,int o,int s)
{
if(l==r)
{
sum[o]=0;
ans[++tot]=l;
return;
}
int ls=o*2,rs=o*2+1;
int m=(l+r)/2;
if(sum[ls]>=s)
query(l,m,ls,s);
else
query(m+1,r,rs,s-sum[ls]);
sum[o]=sum[ls]+sum[rs];
}
int main()
{
int T,s;
scanf("%d",&T);
while(T--)
{
scanf("%d",&k);
build(1,k,1);
tot=0;
for(int i=1;i<=k;i++)
{
scanf("%d",&s);
query(1,k,1,s+1);
}
for(int i=1;i<k;i++)
printf("%d ",ans[i]);
printf("%d\n",ans[k]);
}
}