1~n,乱序排列,告诉每个位置的前面的数字中比它小的数的个数,求每个位置的数字是多少
Sample Input
5 //五头牛
1 //对于第2头牛来说,前面有1头比它小
2
1
0
Sample Output
2
4
5
3
1

Sol:查找第a[i]+1小的数字,可以权值线段树或树状数组.

下面这个是暴力程序,注意我们要选择的那个位置必须是“1”,代表尚未使用过的。。这是整个全篇所有程序的要点。。。

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
int a[8010],f[8010],ans[8010];
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=2;i<=n;i++)
        scanf("%d",&a[i]);
    for(int i=n;i>=1;i--)
    {
        int sum=0;
        for(int j=1;j<=n;j++)
        {
            if(!f[j])sum++;
            if(sum==a[i]+1)
            {
                ans[i]=j;
                f[j]=1;
                break;
            }
        }
    }
    for(int i=1;i<=n;i++)
        printf("%d\n",ans[i]);
    return 0;
}

  下面是树状数组,二分位置的。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int a[10000],c[10000],h[10000],n;

int ask(int x)
{
	int ans=0;
	for(;x;x-=x&-x) ans+=c[x];
	return ans;
}

void add(int x,int y)
{
	for(;x<=n;x+=x&-x) c[x]+=y;
}

int query(int x)
{
	int l=1,r=n,loc=n;
	while(l<=r) //二分查找位置
	{
		int mid=(l+r)/2;
		if(ask(mid)==x) 
		   {
		    	if (mid<loc)
			        loc=mid;
			    r=mid-1;
					
			}
		else
	     	if (ask(mid)<x)
	            l=mid+1; 
	        else 
		         r=mid-1;
	}
//	cout<<x<<"          "<<loc<<endl;
	return loc;
}

int main()
{
	cin>>n;
	for(int i=2;i<=n;i++) scanf("%d",&a[i]);
//	a[1]=0;
	memset(c,0,sizeof(c));
	for(int i=1;i<=n;i++) add(i,1);
	for(int i=n;i;i--)
	{
	    h[i]=query(a[i]+1);
	    add(h[i],-1);
	}
	for(int i=1;i<=n;i++) cout<<h[i]<<endl;
	//system("pause");
}

 

 

不用二分的话,直接在Bit中找第K小的,并且位置要尽量靠左时,

不能直接去找,而是应该找第 K-1小的,然后位置再后移动1位。

因为在BIT中是先加大区间,再加小区间,例如我们对1100这一段,我们要找到前缀和为2的,其实前2个数字的前缀和就为2了

但在BIT中会先加区间为4的。

 

 

 

 

  假设给定数列如下:1111111100110011

STEP 1:保证找到一个位置,其前缀为a[i],并且这个位置越靠后越好,也就是说再多1位,总和就超过a[i]了,如果这个位置靠前的话,则后面一位可能是0,再多加1位,也不能超过a[i]。

为什么不能跟从前一样直接找第一个位置,其前缀和为a[i]+1呢?

因为我们加的区间是从大到小,例如0010,前四个数字之和为1,前3个数字之和也为1.

用现在这种倍增的加法,无法保证找到最靠前的位置。


设a[i]=10
此时我们找的ans是最靠后一个位置,其前缀的为10
此时会找到倒数第3个位置。
程序是这样做到的
先试图去加c[16]=12,发现不能加
先试图去加c[8]=8,发现能加,就加上
再试图去加第8个位置后面连续4个数字即c[12]=2,发现能加,就加上
再试图去加第12个位置后面连续2个数字,c[14]=0,发现能加,也加上
再加图去加第14个位置后面连续1个数字,c[15]=1,发现不能加了,
于是第14位就是我们要的,其数字和为10.

整个过程跟求Lca倍增法非常类似

如果是从前二分找位置的话,找的是某个位置其前缀和为11.
这个位置是第一个前缀和为11的,即倒数第2个位置


于是我们此时找到的结果要加1

 

这个条件ans + (1<<p) <=n
STEP 2:
是保证要加的数字个数<=n,例如n=14时
我们让其先加8个,再加4个,2个,1个。这样是可以加过头的

 

也有可能N=16

我们一开始就加了16,然后再加的位置就是C[24]了,这个位置根本是不存在的。

#include<bits/stdc++.h> 
using namespace std;       
 
inline void read(int &x)
{
int k=0;char f=1;
char c=getchar();
for(;!isdigit(c);
c=getchar())
if(c=='-')
f=-1;
for(;isdigit(c);
c=getchar())
k=k*10+c-'0';
x=k*f;
}

const int maxn=1e5+34;
 
int n;
int a[maxn],b[maxn],c[maxn*2],h[maxn];
 
int lowbit(int x){return x&-x;}
int ask(int x){
    int ans=0;
    for(;x;x-=lowbit(x))ans+=c[x];
    return ans;
}
void add(int x,int y){
    for(;x<=n;x+=lowbit(x))c[x]+=y;
}
 
int main(){
    scanf("%d",&n);
    for(int i=2;i<=n;i++)
	  {
	          scanf("%d",&a[i]);
	  }
    for(int i=1;i<=n;i++)
	   {
	         b[i]=1;
			 add(i,1);
	   }
			
         
    int lim=(int)log2(n);
    for(int i=n;i>=1;i--)
    {
        int ans=0,sum=0;
        
        for(int p=lim;p>=0;p--)
        {
            if(ans + (1<<p) <=n && sum+c[ans+(1<<p)]<=a[i])

            //ans+1<<p代表要加哪一段的数字
	    {
                sum+=c[ans+(1<<p)];
                ans+=(1<<p);
            }
        }
        h[i]=ans+1;
        add(ans+1,-1);
    }
    for(int i=1;i<=n;i++)printf("%d\n",h[i]);
 
}

  

 

  

 

  

#include<bits/stdc++.h>
using namespace std;
int n,a[8010],ans[8010];
int sum[80010];
void insert(int p,int l,int r,int x,int val) 
{
if(l==r) 
{
    sum[p]+=val;
    return;
}
int mid=(l+r)/2;
if(x<=mid) 
   insert(p*2,l,mid,x,val);
else 
    insert(p*2+1,mid+1,r,x,val);
sum[p]=sum[p*2]+sum[p*2+1];
}
int kth(int p,int l,int r,int x) 
{
if(l==r) return l;
int mid=(l+r)/2;
if(sum[p*2]<x) 
   return kth(p*2+1,mid+1,r,x-sum[p*2]);
else 
   return kth(p*2,l,mid,x);
}
int main() 
{
scanf("%d",&n);
for(int i=2;i<=n;i++) 
    scanf("%d",&a[i]);
for(int i=1;i<=n;i++) 
   a[i]++;
for(int i=1;i<=n;i++) 
    insert(1,1,n,i,1);
for(int i=n;i>=1;i--) 
{
ans[i]=kth(1,1,n,a[i]);
insert(1,1,n,ans[i],-1);
}
for(int i=1;i<=n;i++) 
    printf("%d\n",ans[i]);
}

  

 这个程序,更好看一点吧。

#include <cstdio>
#include <algorithm>
using namespace std;

const int maxn = 8e3 + 10;
int tree[maxn*8], p[maxn], res[maxn];

void build(int l, int r, int dex)
{
	if(l == r) tree[dex] = 1;
	else{
		int mid = (l+r)>>1;
		build(l, mid, dex*2), build(mid+1, r, dex*2+1);
		tree[dex] = tree[dex*2] + tree[dex*2+1];
	}
}
int query(int l, int r, int dex, int k)
{
	if(l == r) return l;
	int mid = (l+r)>>1;
	if(k <= tree[dex*2]) return query(l, mid, dex*2, k);
	else return query(mid+1, r, dex*2+1, k - tree[dex*2]);
 } 
void update(int l, int r, int dex, int x)
{
	if(l <= x && r >= x)
	{
		tree[dex]--;  //这个区间的数字个数要减少一个
		if(l != r)
		{
			int mid = (l+r)>>1;
			update(l, mid, dex*2, x), update(mid+1, r, dex*2+1, x);
		}
	}
 } 

int main()
{
	int n;
	while(scanf("%d", &n) != EOF){
		for(int i = 2; i <= n; i++) scanf("%d", &p[i]);
		p[0] = 0;
		build(1, n, 1);
		for(int i = n; i; i--){
			res[i] = query(1, n, 1, p[i]+1);
			update(1, n, 1, res[i]);
		}
		for(int i = 1; i <= n; i++) printf("%d\n", res[i]);		
	}
}