大致思路:
一看,序列,数据很大,暴力可能就是去遍历,题中还有“前面有多少”的字眼,感觉就是用树状数组做。
正向分析感觉有点困难,我们反向来想,如果是序列的最后一个人,他前面有a人的编号比自己小,而他前面就是除他之外的所有人,所以他的编号应该就是a+1.
那么倒数第二个人呢?他前面的人是除他 而且除去最后一个人之外的所有人,所以不仅考虑前面还要考虑后面!
我现在知道的信息是:编号应该是所有比他编号小的人数+1,而所有比他编号小的人数=前面比他编号小的+后面比他编号小的。而前面比他编号小的就是a[i],而后面比他编号小的。。。。想到逆序对的思路了吗。。。。。就是sum(i)!
因此,我不知道它会在哪,但是我知道它应该在哪——需要根据条件“m-1=a[i]+sum(i)”去寻找它的位置——》》二分法!!!
当然,对于最后一个人,显然它的a[i]没有意义,所以我就去看看最后哪个位子是空的,那么它就应该在那儿。
但是对于二分法的细节问题又来了:只要满足这个条件就行了?其实还必须要考虑“冲突机制”!!!!(这个真的是学到了)。你要去想有没有可能位子上有冲突,思考的答案是有的——如果你二分法落到一个已经有人坐的位子上,而碰巧,这个人的a[i]和你的a[i]相等,而同一个位子上肯定sum(i)也相等,所以必然满足条件m-1==sum(i)+a[i],那么怎么办?他们的大小关系如何?有可能这两个数(设为a,b,b是先坐上位子的那个人,也就是说b在原序列中是排在a后面的)相等 ; 有可能a<b吗?没有可能!因为a[b]肯定包含了a,而a又在b前面,那么a[a]肯定小于a[b] ; 有可能a>b吗?可能! 所以,我们应该把a往后移动,你会发现往后移的过程中 m也在+1,同时sum(m)也有可能在+1 ,也就一直满足m-1==sum(i)+a[i](其中一直在冲突),直到不满足的那一刻!你就应该退一步回去,那个位子肯定是空的,也是属于你的。
AC代码:
#include<iostream>
#include<bits/stdc++.h>
using namespace std;
int c[500005];
long long sum(int i)
{
long long res=0;
while(i>0)
{
res+=c[i];
i-=i&(-i);
}
return res;
}
int n;
void add(int i)
{
while(i<=n)
{
c[i]++;
i+=i&(-i);
}
}
int a[500005];
int ans[500005];
bool flag[500005];
int main()
{
cin>>n;
for(int i=2;i<=n;i++)
{
scanf("%d",&a[i]);
}
add(a[n]+1); //最后一个数的编号一定是比他编号小的数+1
ans[n]=a[n]+1;
flag[a[n]+1]=true;
//从后往前分析:已知前面比我小的,但是我的位置也该考虑后面比我小的。后面的已插入,用sum计算数量
//问题又来了:不知道该放在哪里!正确位置mid应满足条件:mid-1==sum(mid-1)+a[i]
//二分法
for(int i=n-1;i>=2;i--)
{
int l=a[i]+1;
int r=n+1; //!!!!!
int mid;
while(l<r)
{
mid=(l+r)/2;
if(mid-1==sum(mid-1)+a[i])
{
while(mid<=n && mid-1==sum(mid-1)+a[i])
{
mid++;
}
mid--;
break;
}
else if(mid-1<sum(mid-1)+a[i])
l=mid+1;
else if(mid-1>sum(mid-1)+a[i])
r=mid; //!!!!!
}
add(mid);
ans[i]=mid;
flag[mid]=true;
}
for(int mid=1;mid<=n;mid++)
{
if(flag[mid]==false) //赋值空缺
{
ans[1]=mid;
break;
}
}
for(int i=1;i<=n;i++)
printf("%d\n",ans[i]);
return 0;
}
注意代码中注释了感叹号的两个地方,是二分法的注意事项。
我以前写二分查询,就是l=mid+1 r=mid-1,谁想今天觉得应该改改这个写法了(因为我这样写在这道题通不过)。
首先,r的初值应该是一个取不到的值(即能取到的最大值+1,如题中的n+1)。为什么呢?因为mid=(l+r)/2 ,会发现由于除法结果会舍去小数部分的性质,你其实永远都取不到r的值!!!!!第二点,你应该在mid值偏大即查询的key值在左边的时候使r=mid,此时如果r=mid-1那么你有可能错过mid-1这个可能解。而l仍然是mid+1,因为本来你l就取得到的,mid肯定不对。
学到就赚到,还要记到。