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
",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
",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
",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
", res[i]);
}
}