描述
N(2 <= N <= 8,000)母牛具有1..N范围内的独特品牌。在一个表现不佳的壮观表现中,他们参观了附近的“水坑”,并在晚餐前喝了几杯啤酒。当是时候排队吃晚餐时,他们没有按照品牌所需的数字顺序排列。
遗憾的是,FJ没有办法对它们进行排序。此外,他不善于观察问题。他没有记下每头牛的品牌,而是确定了一个相当愚蠢的统计数据:对于每头排队的牛,他都知道排在那头牛之前的奶牛数量实际上比那头牛的品牌小。
根据这些数据,告诉FJ奶牛的确切排序。
输入
*第1行:单个整数,N
*行2..N:这些N-1行描述了在给定的奶牛排行之前并且品牌小于该奶牛的奶牛数量。当然,没有奶牛排在第一头牛的前面,所以她没有列出。输入的第2行描述了品牌小于插槽#2中的奶牛的前面奶牛的数量; 第3行描述了品牌小于3号槽中奶牛的前奶牛数量; 等等。
输出
*第1..N行:N行输出中的每一行都告诉品牌一头牛。输出的第1行告诉品牌第一头牛; 第2行告诉第二头牛的品牌; 等等。
样本输入
5
1
2
1
0
样本输出
2
4
5
3
1
资源
解法1:暴力
数据规模n≤8000,O(n^2)的复杂度可以AC,每次找出最小点,确定该点后,就可以逐步确定排位了
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int a[10005];
int ans[10005];
int main(){
int n,cnt;
while(~scanf("%d",&n)){
a[1] = 0;
cnt = 1;
memset(ans,0,sizeof(ans));
for(int i=2;i<=n;i++)
scanf("%d",&a[i]);
for(int k=1;k<=n;k++)
for(int i=n;i>=1;i--)
if(a[i]==0){
ans[i] = cnt++;
for(int j=i;j<=n;j++)
a[j]--;//这点的排位确定了,对其后面的点产生了影响,因为该点是最小点,他后面的点都要进一位
a[i] = 9999999;//相等于删除该点的权值
break;
}
for(int i=1;i<=n;i++)
printf("%d\n",ans[i]);
}
return 0;
}
解法2:线段树
每次也是从最后一个点开始确定该点的排位,因为该点的pre数组值是包括了整个数组,换句话说,就是1-n中排第几小是确定的,所以从它入手可以逐步确定答案,找出该点的编号后,还需要维护线段树的len值,即该区间还有多少个未确定的点
#include<iostream>
#include<cstdio>
using namespace std;
const int MAX = 10000+5;
struct {
int l,r,len;//len记录有多少个点未被删的点
} tree[4*MAX];
int pre[MAX],ans[MAX];
void buildTree(int u,int l,int r){
tree[u].l = l;
tree[u].r = r;
tree[u].len = r-l+1;
if(l==r)
return;
int mid = l + (r-l)/2;
buildTree(2*u,l,mid);
buildTree(2*u+1,mid+1,r);
}
int query(int u,int num){
tree[u].len--;
if(tree[u].l == tree[u].r)
return tree[u].l;
if(tree[2*u].len>=num)//前面一半的点的数量已经可以够确定排位了就在左孩子找,否则在右孩子找num-左孩子总的点数
return query(2*u,num);
else
return query(2*u+1,num-tree[2*u].len);
}
int main(){
int n;
while(~scanf("%d",&n)){
pre[1] = 0;
for(int i=2;i<=n;i++)
scanf("%d",&pre[i]);
buildTree(1,1,n);
for(int i=n;i>=1;i--)
ans[i] = query(1,pre[i]+1);
for(int i=1;i<=n;i++)
printf("%d\n",ans[i]);
}
return 0;
}
解法3:二分+树状数组
树状数组的模板
lowbit(x) = x & -x
功能:找到x的二进制数的最后一个1
x
1
2
3
4
5
6
7
8
9
x的二进制
1
10
11
100
101
110
111
1000
1001
lowbit(x)
1
2
1
4
1
2
1
8
1
#define lowbit(x) ((x) & - (x))
void add(int x, int d){ //ax = ax + d
while(x <= n) {
tree[x] += d; x += lowbit(x);
}
}
int sum(int x) { //求和:sum=a1+...+ax
int sum = 0;
while(x > 0){
sum += tree[x]; x -= lowbit(x);
}
return sum;
}
基于tree[]的计算
(1)求和 sum = a1 + ... + ax
利用tree[]数组求sum,例如:
sum[8] = tree[8]
sum[7] = tree[7] + tree[6] + tree[4]
sum[9] = tree[9] + tree[8]
以上关系是如何得到的?借助lowbit(x)
例:sum[7] = tree[7] + tree[6] + tree[4]
(1)从7开始,加上tree[7];
(2)7 - lowbit(7) = 6,加上tree[6];
(3)6 - lowbit(6) = 4,加上tree[4];
(4)4 - lowbit(4) = 0,结束。
tree[]的更新
•更改ax,和它相关的tree都会变化。例如改变a3,那么tree[3]、tree[4]、tree[8]...都会改变。
•会影响哪些tree[]? 仍然利用lowbit(x):
(1)更改tree[3];
(2)3 + lowbit(3) = 4,更改tree[4];
(3)4 + lowbit(4) = 8,更改tree[8];
(4)直到最后的tree[n]。
#include<iostream>
#include<cstdio>
using namespace std;
#define lowbit(x) ((x) & -(x))
const int MAX = 100005;
int tree[MAX],pre[MAX],ans[MAX];
int n;
void add(int x,int d){
while(x<=n){
tree[x]+=d;
x+=lowbit(x);
}
}
int sum(int x){
int sum=0;
while(x>0){
sum+=tree[x];
x-=lowbit(x);
}
return sum;
}
int findpos(int x){//套用二分答案的模板
int l=1,r=n,ans=1;
while(l<=r){
int mid = l + (r-l)/2;
if(sum(mid)>=x){
r = mid-1;
ans = mid;
}else
l = mid+1;
}
return ans;
}
int main(){
while(~scanf("%d",&n)){
pre[1] = 0;
for(int i=2;i<=n;i++)
scanf("%d",&pre[i]);
for(int i=1;i<=n;i++)
tree[i] = lowbit(i);//初始化tree数组
for(int i=n;i>=1;i--){
int x = findpos(pre[i]+1);
ans[i] = x;
add(x,-1);//维护树状数组
}
for(int i=1;i<=n;i++)
printf("%d\n",ans[i]);
}
return 0;
}