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;
}