zoukankan      html  css  js  c++  java
  • POJ 2082 三种解法(暴力+树状数组+线段树)

    题目链接:http://poj.org/problem?id=2182

    题目给出一个n,代表牛的数量,编号是1-n,另外给出n-1个数,代表在某个位置之前有多少数是比这个位置的数小的,1之前没有比它小的,所以不给出。想法是最后一个数可以最先确定,如果最后一个数前面有a个数比他小,那他就是第a+1个数。从后往前扫描一遍就可以得出结果,时间复杂度是O(n^2),在本题的时间复杂度之下还是可以用的。用树状数组的话,时间复杂度是O(nlogn)。

    模拟代码:

     1 #include<cstdio>
     2 using namespace std;
     3 int n,pre[100005],a[100005],b[100005];
     4 int main()
     5 {
     6     scanf("%d",&n);
     7     for(int i=2;i<=n;i++) 
     8         scanf("%d",&pre[i]);
     9     pre[1]=0;
    10     for(int i=1;i<=n;i++)a[i]=i;
    11     for(int i=n;i>=1;i--)
    12     {
    13         int k=0;
    14         for(int j=1;j<=n;j++)
    15         {
    16             if(a[j]==-1)continue;//该数在后面已经填好 
    17             else
    18             {
    19                 k++;
    20                 if(k==pre[i]+1)
    21                 {
    22                     b[i]=a[j];
    23                     a[j]=-1;
    24                 }
    25             }
    26         }
    27     } 
    28     for(int i=1;i<=n;i++)
    29     {
    30         printf("%d
    ",b[i]);
    31     }
    32 }

    当数据规模比较大的时候可以用以下的线段树的代码:本题就是查询区间第k小的问题,可以用权值线段树的方法,权值线段树中每个结点代表的是该区间中一共有多少个数,所以在查询的时候将路径上的每个结点的值-1便可以做到标记某个位置被占用。

    代码如下:

     1 #include<cstdio>
     2 using namespace std;
     3 const int maxn=100005;
     4 int n,pre[maxn],sum[maxn<<2],b[maxn];
     5 void build(int l,int r,int rt)
     6 {
     7     if(l==r)
     8     {
     9         sum[rt]=1;
    10         return;
    11     }
    12     int mid=l+r>>1;
    13     build(l,mid,rt<<1);
    14     build(mid+1,r,rt<<1|1);
    15     sum[rt]=sum[rt<<1]+sum[rt<<1|1];
    16 }
    17 int query(int l,int r,int rt,int k)
    18 {
    19     sum[rt]--;
    20     if(l==r)return l;
    21     int mid=l+r>>1;
    22     if(k<=sum[rt<<1])return query(l,mid,rt<<1,k);
    23     else return query(mid+1,r,rt<<1|1,k-sum[rt<<1]);
    24 }
    25 int main()
    26 {
    27     scanf("%d",&n);
    28     for(int i=2;i<=n;i++)scanf("%d",&pre[i]);
    29     pre[1]=0;
    30     build(1,n,1);
    31     for(int i=n;i>=1;i--)
    32     {
    33         b[i]=query(1,n,1,pre[i]+1);
    34     }
    35     for(int i=1;i<=n;i++)
    36     {
    37         printf("%d
    ",b[i]);
    38     }
    39 }

    下面再用树状数组求区间第k小,树状数组的代码相对来说更加简洁一些,原理跟线段树的原理一样,每个位置存1,表示该位置没有被占用,然后每次都寻找一个第k大的位置,更新点为0,前缀和等于k的位置就是第k大,所以就用二分的方法查询前缀和等于k的位置。总的时间复杂度大约是O(nlog^2n)。

    代码如下:

     1 #include<cstdio>
     2 using namespace std;
     3 #define lowbit(x) (x&-(x))
     4 #define maxn 10005
     5 int n, pre[maxn],c[maxn],b[maxn];
     6 int query(int x)
     7 {
     8     int ans=0;
     9     for(int i=x;i;i-=lowbit(i))ans+=c[i];
    10     return ans;
    11 }
    12 int findpos(int k)
    13 {
    14     int l=1,r=n,mid;
    15     while(l<r)
    16     {
    17         mid=l+r>>1;
    18         if(query(mid)<k)
    19         {
    20             l=mid+1;
    21         }
    22         else r=mid;
    23     }
    24     return l;
    25 }
    26 void update(int x,int C)
    27 {
    28     for(int i=x;i<=n;i+=lowbit(i))
    29     {
    30         c[i]+=C;
    31     }
    32 }
    33 int main()
    34 {
    35     scanf("%d",&n);
    36     for(int i=2;i<=n;i++)scanf("%d",&pre[i]);
    37     for(int i=1;i<=n;i++)c[i]=lowbit(i);
    38     pre[1]=0;
    39     for(int i=n;i>=1;i--)
    40     {
    41         int x=findpos(pre[i]+1);
    42         update(x,-1);
    43         b[i]=x;
    44     }
    45     for(int i=1;i<=n;i++)printf("%d
    ",b[i]);
    46 } 

    总结一下时间效率,线段树的时间花费是0ms,树状数组的时间花费是47ms,模拟的时间花费是743ms

  • 相关阅读:
    HBase 操作
    HBase Java API 例子
    微信浏览器拖动出现黑色/白色背景、网址问题解决方案
    layui弹出层置顶弹出
    使用layui时,ajax执行后,重新渲染页面的方法
    宝塔更新
    js 播放音频文件 兼容火狐 谷歌浏览器
    SAP断点
    error_log 用法
    SE开头的事务代码
  • 原文地址:https://www.cnblogs.com/randy-lo/p/12611711.html
Copyright © 2011-2022 走看看