UVA 11525 - Permutation(BIT+二分)
题目链接 https://cn.vjudge.net/problem/UVA-11525
【题意】
给定整数k,输出1~k的所有排列中,按照字典序从小到大排序后的第n个(编号从0开始)由于n可能很大,本题用k个整数S[1],S[2]…S[k]来间接给出n,方式如下
n=S[1]×(k-1)!+S[2]×(k-2)!+…+S[k]×0!
【输入格式】
多组输入,第一行为数据组数T(T<=10)每组数据第一行为一个整数k(1<=k<=50000)第二行包含k个整数S[i](0<=S[i]<=k-i)
【输出格式】
对每组数据,输出满足题意的1~k的一个全排列
【思路】
做这道题之前首先要懂一个关于全排列的知识点就是康托展开,用于计算第几个全排列是多少,或者计算某个全排列排在第几位。详细讲解见百度百科:康托展开
然后这道题目中给出的式子刚好就是康托展开式,其中S[i]表示的就是这个排列里面第i个位置的右边有几个数字比它自己小,比如说排列34152,那么s[2]=2,因为在第二位的4右边有2个数字比4要小(1,2),根据这个性质就不难求出原来的排列是多少了。
我们可以先把1~k都放入一个有序集合中,然后从高位向低位考虑,也就是从S[1]~S[k]考虑,每次找出一个最小值x,使得x>S[i],那么在答案中这一位上就应该是x了。可以用树状数组实现这个有序集合,然后二分查找每个x的值.
#include<bits/stdc++.h>
using namespace std;
const int maxn=50005;
int n;
int a[maxn];
int bit[maxn];
vector<int> ans;
void add(int i,int x){
while(i<=n){
bit[i]+=x;
i+=i&-i;
}
}
int sum(int i){
int ans=0;
while(i>0){
ans+=bit[i];
i-=i&-i;
}
return ans;
}
int main(){
int T;
scanf("%d",&T);
while(T--){
scanf("%d",&n);
memset(bit,0,sizeof(bit));
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
add(i,1);
}
ans.clear();
for(int i=1;i<=n;++i){
int le=0,ri=n;
while(le+1<ri){
int mid=(le+ri)>>1;
if(sum(mid)>a[i]) ri=mid;
else le=mid;
}
add(ri,-1);
ans.push_back(ri);
}
for(int i=0;i<ans.size();++i) printf("%d%c",ans[i],i+1==ans.size()?'\n':' ');
}
return 0;
}