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\n",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\n",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\n",b[i]);
46 } 

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

 

posted @ 2020-04-01 12:29  WA自动机~  阅读(212)  评论(0编辑  收藏  举报