标签:前缀 ace content 全排列 for 标记 val == its
给你一个数字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啦~~~
如果有哪些地方写的不对的话欢迎各位巨佬指正,如果您觉得我写的对您的学习有帮助的话,请给博主一个小小的赞,您的支持将是我前进的动力~~~
标签:前缀 ace content 全排列 for 标记 val == its
原文地址:https://www.cnblogs.com/sbwll/p/13197480.html