【树状数组】奇怪的报数游戏

大致思路

一看,序列,数据很大,暴力可能就是去遍历,题中还有“前面有多少”的字眼,感觉就是用树状数组做。

正向分析感觉有点困难,我们反向来想,如果是序列的最后一个人,他前面有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肯定不对。

学到就赚到,还要记到。




  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值