POJ 2828

 

题意:将一些数字按顺序,插入到数列的制定位置的后面。多次执行后,求最终的序列。
思路:注意到,最后一次添加的数v如果放到了pos位置的后面,那它最终一定在pos+1这个位置。所以考虑倒着插入这些数。
当我们动态插入数字v到pos后面时,实际要插入的位置,是一个保证前面已经插入了pos个数字的最小位置pos2。假如f[x]表示在实际位置x前面有多少个已经插入,则只要x – f[x] = pos就可以放在x位置上了。
最终决定使用线段树的数据结构。f[x]表示的,是x所代表的线段(区间)内已经插入的数字个数。我的程序中,用sum表示在x区间之前还有多少数已经插入,所以最后的判断条件是 x – f[x] – sum = pos。实在没看懂的,可以跟踪一次程序就理解了。
这里提到一个discuss里面问的比较多的问题,为什么线段树的大小要开到200000*3才够。不是规模为n的线段树,开到n*2就够了吗?
n*2的建树方法,适用于动态建树,就是说没有任何一个节点被浪费的建法。而这里我使用的,或者说大多数人使用的这种用x*2表示左子树 x*2+1表示右子树的建法,是不能只开到n*2的,因为有写节点没用到。
举个列子来说,假如线段树的n = 11,你认为应该建树为22或者23,保守点。现在看图。

红色的标记是节点编号,黑色的是区间。
如果你要找[9,9]的区间,就会出现RTE了。
那么开多少才正确呢?
其实,应该是 2^ ( ceiling( log2(n) )+1 ), 即2的log2(n)的上取整加1次幂。
这道题目,用此方法计算,得到实际需要空间大小524288,我开了524300,AC了

 

#include <iostream> #define F(i,a,b) for (int i=a;i<=b;i++) #define FD(i,a,b) for (int i=a;i>=b;i--) using namespace std; const int maxn = 524300; int f[maxn*10], a[maxn], pos[maxn], v[maxn]; void insert(int pos, int v ) { int root = 1, left = 1, right = maxn, mid, chi, sum = 0; while (left < right) { f[root]++; mid = (left + right ) / 2, chi = root * 2; if (mid - sum - f[chi] > pos) { right = mid; root = chi; } else { left = mid + 1 ; root = chi + 1; sum+=f[chi]; } } f[root]++; a[right] = v; } int main() { int n; while (scanf("%d", &n)!=EOF) { memset(f, 0, sizeof(f) ); F(i,1,n) scanf("%d%d", &pos[i], &v[i]); FD(i,n,1) insert(pos[i], v[i]); F(i,1,n) printf("%d ", a[i]); printf("/n"); } return 0; }  

你可能感兴趣的:(数据结构,BI,insert)