题意:原序列为空。依次将人插入到该序列的给定位置。输出最后的序列。因为插入有先后顺序,导致最后实际的位置和原来插入位置不同。
思路:如果用数组或vector去模拟,那因为插入导致元素的移动会让整个程序超时。
我们注意到:对于最后一个人,他在最后序列中的位置就是他的插入位置,即,他的位置是确定的,那么能否从后往前,找到每个人的实际位置呢?答案是肯定的。
我们用一个数组标记每个位置是否已经有人。当从后往前插入时,对于待插入的人,题目中给出的位置m代表的意义是:对于该位置,前面有m个位置没有人(因为,从前往后按照顺序插入时,前m个位置必定有人,那该人才能插入到这个位置。当反过来,从后往前时,我们要给前面的人留出对应的m个位置)。而对前面位置的求和,自然的想到了树状数组。
所以思路为:从后往前,对于每个人,我们二分枚举位置,判断该位置是否满足前面的有m个空位。
复杂度:二分为lgn,树状数组求和为lgn,所以整体的复杂度为n(lgn)^2
坑:不知道为啥,如果把lowbit变成一个函数,就会超时。现在想想,应该是调用lowbit比较频繁,中间的建立和摧毁过程的开销还是挺大的
代码如下:
<pre class="cpp" name="code">#include <bits/stdc++.h>
using namespace std;
const int MAX = 200100;
int N;
int a[MAX];
int b[MAX];
int r[MAX];
int s[MAX];
void update(int x, int data)
{
for(;x <= N; x += ( x&(-x)))
s[x] += data;
}
int getsum(int x)
{
int ans = 0;
for(;x > 0; x -= (x &(-x)))
ans += s[x];
return ans;
}
int main(void)
{
//freopen("input.txt","r",stdin);
while(scanf("%d", &N) != EOF){
for(int i = 0 ; i < N; ++i){
scanf("%d %d", &a[i], &b[i]);
a[i]++;
}
memset(s,0,sizeof(s));
for(int i = 1; i <= N; ++i)
update(i,1);
for(int i = N-1; i >= 0; i--){
int lb = 0, ub = N;
while(lb + 1< ub){
int mid = (ub + lb) / 2;
//printf("%d %d %d\n",i,mid,getsum(mid));
if(getsum(mid) >= a[i]) ub = mid;
else lb = mid;
}
//printf("%d\n",ub);
r[ub] = b[i];
update(ub,-1);
}
for(int i = 1; i <= N; ++i)
printf("%d%c",r[i], i==N?'\n':' ');
}
return 0;
}
思路二:如果仔细的想一想,树状数组是线段树的前缀和的简化。而线段树是天然的二分结构,同时每个节点也保留了自己管辖区间的和的信息。那我们能否利用这个性质对二分枚举位置和求和的两个过程简化成一个呢?
答案是可以的。这两个过程其实可以同时进行。但是由于二分的范围从大到小,我们就要从最高位开始二分,或者说,类似于线段树的递归查找。
代码中,对初始化有一个填充技巧:因为所有的位置的初始值为1,那对于树状数组的每个节点,对应区间的和就是他的lowbit。
代码如下:
#include <bits/stdc++.h>
using namespace std;
const int MAX = 200100;
int s[MAX];
int a[MAX];
int b[MAX];
int r[MAX];
template<class T>
inline bool read(T &n)
{
T signal = 1,x = 0;
char ch = getchar();
while((ch <'0' || ch >'9') && ch != EOF && ch != '-') ch = getchar();
if(ch == EOF) return false;
if(ch == '-') signal = -1,ch = getchar();
while(ch >='0' && ch <= '9'){
x *= 10;
x += ch - '0';
ch =getchar();
}
n = signal * x;
return true;
}
template<class T>
inline void write(T n)
{
if(n < 0) putchar('-'), n = -n;
int len = 0,num[20];
while(n)
num[len++] = n % 10, n /= 10;
if(!len)
num[len++] = 0;
while(len--)
putchar(num[len] + '0');
}
int main(void)
{
// freopen("input.txt","r",stdin);
int n;
while(scanf("%d", &n) != EOF){
for(int i = 0; i < n; ++i)
read(a[i]),read(b[i]),a[i]++;
for(int i = 1; i <= n; ++i)
s[i] = i & (-i);
for(int i = n - 1; i >= 0; --i){
int d = 0, sum = 0, k = a[i];
for(int j = 18; j >=0; j--){
d += 1<<j;
if(d >= n || sum + s[d] >= k) d -= 1<<j;
else sum += s[d];
}
r[++d] = b[i];
for(;d <= n; d += d & (-d))
--s[d];
}
write(r[1]);
for(int i = 2; i <= n; ++i)
putchar(' '),write(r[i]);
putchar('\n');
}
return 0;
}