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