码迷,mamicode.com
首页 > 其他好文 > 详细

[Usaco2003 Open]Lost Cows

时间:2020-06-27 11:29:47      阅读:52      评论:0      收藏:0      [点我收藏+]

标签:前缀   ace   content   全排列   for   标记   val   ==   its   

[Usaco2003 Open]Lost Cows

描述

给你一个数字N,2 <= N <= 8,000 
再给出一个N的全排列,乱序排列 
告诉你从第2个位置到第N个位置,每个位置的前面的数字中比它小的数的个数 
求每个位置的数字是多少  

输入

第一行给出数字N 
接下来N-1行,每行给出一个数字 

输出

有N行 
每行一个数字,代表这个位置上的数字是多少  

样例

输入

5 
1 
2 
1 
0 

输出

2 
4 
5 
3 
1 
思路:
看看题面,一看就是倒循环来搞,再看看n,小于等于8000,我一惊,这道题不是暴力就可以过吗,于是5分钟敲了一个暴力代码,具体思
路见代码:
 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int a[10001];//记录比它小的数字个数 
 4 int f[10001];//逻辑数组
 5 int ans[10001];//最终计算的结果
 6 int main()
 7 {
 8     int n;
 9     scanf("%d",&n);
10     for(int i=2;i<=n;i++)
11         scanf("%d",&a[i]);
12     for(int i=1;i<=n;i++)
13         f[i]=1;//初始值全部为1 
14     ans[n]=a[n]+1;//最后一个数字显然是a[n]+1(前面有a[n]个比它小的) 
15     f[ans[n]]=0;//这个数字用过了,打上标记 
16     for(int i=n-1;i>=1;i--)//从后往前推 
17     {
18         int num=0;//用num记录前面有多少个比它小的 
19         for(int j=1;i<=n;j++)//枚举每一个数(vis数组) 
20         {
21             if(f[j]==1)//如果这个数没有被用过 
22             {
23                 num++;//统计比它小的数字个数
24                 if(num==a[i]+1) //找到了这个数 
25                 {
26                     f[j]=0;//打上标记 
27                     ans[i]=j;//赋值 
28                     break;//因为我们是要找最小的符合条件的,因此要break
29                     //否则有可能会在后面再找到符合条件的,把它更新了。。。 
30                 }
31             }
32         }
33     }
34     for(int i=1;i<=n;i++)
35         printf("%d\n",ans[i]);
36     return 0; 
37 }

我们再来看看这个代码,注意到第21,23行,这是不是就是统计在a[i]+1这个数前面有多少个f[j]为1?也就是一个求前缀和的过程,既然是求前缀和,那我们是不是可以

用树状数组来进行优化?还是和上面那个f[]数组一样,我们首先在每个位置上插入一个1,找到答案了就在这个位置上插上-1(该位置变成0),然后现在此问题就变成

了让我们用树状数组来维护一个01串了,我们要找的答案其实就是位置<=a[i]+1的数有多少个是1。。。。

我们来模拟一下样例1 2 1 0

首先预处理 1 1 1 1 1 ,

然后我们倒循环找ask(0+1)=ask(1)=1,所以ans[n]=1,同时把第一个位置变成0,原序列变成 0 1 1 1 1

接着找ask(1+1)=ask(2)=3,所以ans[4]=3,和上面一样,原序列变成 0 1 0 1 1

再找ask(2+1)=ask(3)=5,所以ans[3]=5,原序列变成 0 1 0 1 0

我们再找ask(1+1)=ask(2),但是我们注意到上面这个序列,前面四个数的前缀和为2,前面五个数的前缀和也为2!!所以这个时候我们就要贪心一点,尽量选

靠左边的数,于是ans[2]=4,原序列变成 0 1 0 0 0,

最后找ask(0+1)=1,ans=[1]=2,原序列变成0 0 0 0 0,

于是最后的答案便是 2 4 5 3 1。。。。具体细节见代码:

 1 #include<bits/stdc++.h>
 2 #define ll long long
 3 using namespace std;
 4 ll n,ans[80001],sum[500010],a[500010];
 5 ll lowbit(ll n) {
 6     return n&(-n);
 7 }
 8 void add(ll x,ll val) {
 9     while(x<=n) {
10         sum[x]+=val;
11         x+=lowbit(x);
12     }
13 }
14 ll ask(ll x) {
15     ll t=0;
16     while(x) {
17         t+=sum[x];
18         x-=lowbit(x);
19     }
20     return t;
21 }
22 ll serch(ll x)
23 {
24     ll l=1,r=n,loc=n;//loc代表答案 
25     while(l<=r)
26     {
27         ll mid=(l+r)>>1;
28         if(ask(mid)==x)//如果找到了符合条件的数 
29         {
30             if(mid<loc)//如果mid在loc左边(比loc小) 
31                 loc=mid;//我们要找最靠左的答案
32                 //因此如果发现了比它小的就更新 
33             r=mid-1;//同时把右端点更新,尝试去找更小的数 
34         }
35         else if(ask(mid)>x)//如果前缀和大于x,说明答案在mid左边 
36         r=mid-1;//右端点移一下 
37         else
38         l=mid+1;//否则就在左边,移一下 
39     }
40     return loc;
41 }
42 int main() {
43     scanf("%lld",&n);
44     for(ll i=2; i<=n; i++) 
45         scanf("%lld",&a[i]);
46     for(ll i=1;i<=n;i++)
47     add(i,1);//预处理,每个位置插上1 
48     for(ll i=n; i>=1; i--) {//从后往前推 
49         ll k=serch(a[i]+1);//二分找答案 
50         add(k,-1);//把这个位置打上标记,用过了 
51         ans[i]=k;//储存起来 
52     }
53     for(ll i=1;i<=n;i++)
54     printf("%lld\n",ans[i]);
55     return 0;
56 }

于是,我们就快乐地AC啦~~~

如果有哪些地方写的不对的话欢迎各位巨佬指正,如果您觉得我写的对您的学习有帮助的话,请给博主一个小小的赞,您的支持将是我前进的动力~~~

[Usaco2003 Open]Lost Cows

标签:前缀   ace   content   全排列   for   标记   val   ==   its   

原文地址:https://www.cnblogs.com/sbwll/p/13197480.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!