NOIP 2017 Day2 题3:列队 线段树

http://blog.csdn.net/qq_38678604/article/details/78575672

 在思考

摘自:http://blog.csdn.net/qq_38678604/article/details/78575672#reply

  线段树 


前50分: 前50分q很小,用O(q^2)倒推就好了

把之前的出队位置记下来,若当前询问为(x,y),上一个为(x1,y1)  

若(x,y) == (n,m),那么就转移到(x1,y1)
假若y == m且x1 <= x,y++
假若x == n且y1 <= y,x++
最后就会推回原来的位置

30分x=1
X=1就是一条链,我们每次需要从区间选出第k大放到末尾,你能想到什么?splay?是的。我考场写了splay,亲测10分【可能是我没写好】


求区间第K大可以用线段树,以值为区间建树,区间和表示这段区间的数出现了多少次,然后就从根节点开始,根据左子树与k的大小比较选择向左向右查找,最终到达的叶子就是我们要找的第k大的值

后面放进来的值我们可以看做是m + 1,m + 2,先存到一个vector表里面,假若我们求出的k大值超出了m,就减去m从表中直接找出


100分
其实30分算法提示的已经很明显了,或者说已经可以当正解了
正解我们只需对每一行建一棵线段树,对最后一列单独建一棵线段树,同样也开相同数量的vector表一起维护就好了
线段树会爆空间怎么办?动态开节点呗。一开始每一个值都是存在的,所以新建节点的值可以直接确定。为了方便,我们对值取反,用0表示出现过一遍,这样求左子树大小只需mid - l + 1 - siz[ls]就好了,删点就令其+1


#include 
#define LL long long int

using namespace std;
const int maxn = 600005,maxm = 20000005,INF = 1000000000;

int n,m,q,Max;

vector G[maxn];
/**
我们常用的线段树是用自然数作为下标,如1是根,左右儿子就是1*2,1*2+1  , 但对动态开节点的线段树,
我们对每个节点人为标号,然后用两个儿子指针替代原来的*2查找  **/
//也就是说,动态开节点的数组的标号已经乱了。 
struct node{
      int sum,ls,rs;
      node(){
         ls = rs = 0;
      }
} e[maxm];

int siz = 0,rt[maxn],pos;
/**
rt数组储存的是根节点的编号,每一行都对应一棵线段树,也就对应着一个根
这里所说的多个线段树并不是二维线段树 ,而是相互独立的一维线段树,分别维护各自行的信息
**/

void modifyt(int& u,int l,int r){  //
	if (!u) u = ++siz;   //加入没有节点,在数组里面新增节点(新增儿子)
  //这种动态的方法,可以节省空间, 如果用传统方法,显然存不了  3*10^5  *  3 *10 ^  5
	e[u].sum++;
	if(l < r) {
		int mid = l + r >> 1;
		if (mid >= pos) modify(e[u].ls,l,mid);
		else modify(e[u].rs,mid + 1,r);
	}
}

/**查询 从第u节点开始,查询第k号位置上理论上已经被哪个新的位置号代替了  (位置号从1开始编号)
我们开的线段树维护的区间和代表该区间数字被使用的次数,查询第K个时,
若左区间长度 - 左区间使用总次数 大于等于 K【即剩余的使用次数】,
说明第K个在左区间,否则在右区间,最后会递归到叶子节点,就是我们要找的位置  **/
//参考了 BIT 树状数组,求冒泡排序的交换次数,豁然开朗。
int Query(int u,int left,int right,int k){
	if (left == right) return left;
	int mid = (left + right) >> 1;
	int sizl = mid - left + 1 - e[ e[u].ls].sum;  //
	if (sizl >= k) return Query( e[u].ls, left, mid, k );
	else return Query( e[u].rs, mid + 1, right, k - sizl);
}

LL Del_r(int x,LL v){
	pos = Query( rt[n + 1], 1, Max, x);
	modify( rt[n + 1], 1, Max);
	LL ans = pos <= n ? 1ll * pos * m: G[n + 1][pos - n - 1];
	G[n + 1].push_back(v ? v : ans);
	return ans;
}

//从第x行的根节点开始。
LL Del_l(int x,int y){
	pos = Query( rt[x], 1, Max, y);
	modify(rt[x],1,Max);
	LL ans = pos < m ? 1ll * (x - 1) * m + pos : G[x][pos - m];
	G[x].push_back(  Del_r(x,ans)  );
	return ans;
}
int main()
{
	cin >> n >> m >> q;  //n 行, m 列,  q次查询。
	Max = max(m,n) + q;
	while (q--){
		int x,y;
		cin >>x >>y;
		if (y == m) cout <<  Del_r(x,0) << endl ;
		else cout << Del_l(x,y) <


几点思考:

1 线段树的节点数,为2 *n 参看 《挑战程序设计》P169 。这一点很多人有错觉
即使如此,我在纠结 这一题 n,m规模达到了 3*10^5 ,  

2 《挑战程序设计》P169 先开辟了固定数组。线段树还可以动态的开辟数组。 上面的代码就是动态的开辟。
参考这一题:https://www.cnblogs.com/zoewilly/archive/2016/10/26/6001978.html


3 这一题我想还可以使用 树状数组来弄,我试着写一下。 树状数组存不了这么多的点啊。 看来只能用动态的线段树了

#include 
#define LL long long int

using namespace std;
const int maxn = 3001,maxm =3001;

int n,m,q;
1
int bit_row[maxn][maxm];  //横向的   树状数组存不了这么多的点啊。看
int bit_col[maxn];  //竖向的

int sum(int  bit[], int i ){
    int s=0;
    while(i>0){
        s += bit[i];
        i = i - i&(-i);
    }
    return s;
}

void del( int bit[], int i, int x){
    while(i<=m-1){
        bit[i] += x;
        i = i + i&(-i);
    }
}

//参考了 BIT 树状数组,求冒泡排序的交换次数,豁然开朗。
int Query(int x, int y){
	if(y!=m ) return  (x-1)*m +  + y +sum(bit_row, y);
	else return x*m + x+ sum(bit_col, x);
}


int main()
{
	cin >> n >> m >> q;  //n 行, m 列,  q次查询。
	memset(bit_col, 0 ,sizeof(bit_col));
	memset(bit_row, 0 ,sizeof(bit_row));
	while (q--){
		int x,y;
		cin >>x >>y;
		cout << Query(x,y) << endl;
		if (y == m)   del(bit_col, x, 1) ;
		else  {
                del( bit_row[x], y, 1);
                del( bit_col, x, 1);
		}

	}
	return 0;
}


你可能感兴趣的:(信息学奥赛(NOIP),数据结构-线段树,NOIP真题)