uva 11525(线段树)

题目链接: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 }
View Code

解法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 }
View Code
原文地址:https://www.cnblogs.com/frog112111/p/3308502.html