题目链接:http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=2520
题意:有一个排列1~k,求第n个排列,其中n为 ,K(1≤K≤50000),S1, S2 ,…, Sk.(0≤Si≤K-i).
分析:这道题目乍看之下没有什么好的思路,k!太大了,但是仔细看一看就会发现n和康托展开式很类似
如果不知道康托展开的话请看:http://www.doc88.com/p-293361248346.html
http://blog.csdn.net/morgan_xww/article/details/6275460
要求第n个全排列,这不就是逆康托展开吗?
没错,仔细对比逆康托展开的推理过程就会发现,其实第n个全排列中的第i个数就是该排列中未出现过的比si大的第一个数。
比如:2 1 0 则比2大的第一个数是3,3未出现过,所以第一个数是3
比1大的第一个数是2,2未出现过,所以第二个数是2
比0大的第一个数是1,1未出现过,所以第三个数是1
所以结果为3 2 1
再比如:1 0 0 则比1大的第一个数是2,2未出现过,所以第一个数是2
比0 大的第一个数是1,1未出现过,所以第二个数是1
比0大的第一个数是1,但是1,2已经出现过了,所以第三个数是3
以此类推
普通的逆康托展开复杂度是O(n^2),这样对于k<=50000来说肯定是会超时的,可以用线段树(二分+树状数组)优化。
由上面的分析可知,
解法1:
线段树的具体做法同样是把 [1, K] 的数置成 1. 此时每条线段的权所代表的意义为在该区间内还有多少个数可以用。查找大于si的第一个未出现过的数,然后把这个数赋为0。 查找的时候如果左线段可用的数大于等于当前我们查找的数, 说明我们要找的数在左线段, 进入左线段, 查找的数不变; 否则说明在右线段, 进入右线段, 查找的数减去左线段可用的数的数目. 递归返回条件为当前节点代表的线段为单位线段(说明我们已经找到了)。
AC代码如下:
1 #include<stdio.h> 2 #define lson l,m,rt<<1 3 #define rson m+1,r,rt<<1|1 4 const int maxn=50000+10; 5 int tree[maxn<<2],ans; 6 void PushUp(int rt) 7 { 8 tree[rt]=tree[rt<<1]+tree[rt<<1|1]; 9 } 10 void build(int l,int r,int rt) 11 { 12 if(l==r) 13 { 14 tree[rt]=1; 15 return ; 16 } 17 int m=(l+r)>>1; 18 build(lson); 19 build(rson); 20 PushUp(rt); 21 } 22 void update(int p,int x,int l,int r,int rt) 23 { 24 if(l==r) 25 { 26 tree[rt]=x; 27 ans=l; 28 return ; 29 } 30 int m=(l+r)>>1; 31 if(p<=tree[rt<<1]) 32 update(p,x,lson); 33 else 34 update(p-tree[rt<<1],x,rson); 35 PushUp(rt); 36 } 37 int main() 38 { 39 int t,n,i,x; 40 scanf("%d",&t); 41 while(t--) 42 { 43 scanf("%d",&n); 44 build(1,n,1); 45 for(i=0;i<n;i++) 46 { 47 scanf("%d",&x); 48 update(x+1,0,1,n,1); 49 printf("%d",ans); 50 if(i!=n-1) 51 printf(" "); 52 } 53 printf(" "); 54 } 55 return 0; 56 }
解法2:
树状数组的具体做法是初始把 [1, K] 的数置成 1, 然后根据所给 S 数组, 去查找前缀和, 前缀和 sum[N] 代表 [1, N] 内有多少个数可以用. 注意前缀和是单调不减的, 因此我们可以二分, 查找第一个等于我们要找的数/的那个下标, 便是全排列当前位的数, 然后把这个下标里的数置成 0, 同时更新树状数组.
AC代码如下:
1 #include<stdio.h> 2 #include<string.h> 3 const int maxn=50000+10; 4 int c[maxn],num[maxn]; 5 int n; 6 int lowbit(int x) 7 { 8 return x&(-x); 9 } 10 void update(int x,int num) 11 { 12 while(x<=n) 13 { 14 c[x]+=num; 15 x+=lowbit(x); 16 } 17 } 18 int sum(int x) 19 { 20 int ret=0; 21 while(x>0) 22 { 23 ret+=c[x]; 24 x-=lowbit(x); 25 } 26 return ret; 27 } 28 int binary(int x) 29 { 30 int low=1,high=n; 31 while(low<=high) 32 { 33 int m=(low+high)>>1; 34 int cnt=sum(m); 35 if(cnt==x) 36 { 37 if(num[m]) 38 return m; 39 else 40 high=m-1; 41 } 42 else if(cnt<x) 43 low=m+1; 44 else 45 high=m-1; 46 } 47 return 0; 48 } 49 int main() 50 { 51 int t,i,x; 52 scanf("%d",&t); 53 while(t--) 54 { 55 scanf("%d",&n); 56 memset(c,0,sizeof(c)); 57 for(i=1;i<=n;i++) 58 num[i]=1; 59 for(i=1;i<=n;i++) 60 update(i,1); 61 for(i=0;i<n;i++) 62 { 63 scanf("%d",&x); 64 int cnt=binary(x+1); 65 update(cnt,-1); 66 num[cnt]=0; 67 printf("%d",cnt); 68 if(i!=n-1) 69 printf(" "); 70 } 71 printf(" "); 72 } 73 return 0; 74 }