zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    使用控制结构——循环语句——基本循环
    oracle字符类型 char,varchar2,varchar,clob,nvarchar,nclob
    使用控制结构——条件分支语句——多重条件分支
    hduoj 1518square
    运算符重载实现复数的加减乘除
    使用游标——参数游标
    开发PL/SQL子程序——触发器——编译触发器,删除触发器,显示触发器
    NYOJ58最少步数
    使用控制结构——条件分支语句——简单条件
    开发PL/SQL子程序——触发器——使用触发器注意事项
  • 原文地址:https://www.cnblogs.com/frog112111/p/3308502.html
Copyright © 2011-2022 走看看