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("\n");

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("\n");

72     }

73     return 0;

74 }
View Code

 

你可能感兴趣的:(线段树)