NOIP2017_D2T3 列队

本文详细解析了动态开点线段树的实现方法,针对特定问题提出了高效的解决方案。通过将方阵划分为多个区间,利用动态开点线段树支持快速查询和更新操作,实现了O(nlog(n+q))的时间复杂度。文章提供了完整的代码示例,包括节点的更新、查询及插入操作。

题目略(各大OJ上都有)

题解:

我们可以观察到一个规律,就是每次移动都只会改变一行和最后一列(最后一列是关键),于是我们就可以把这个方阵划分成这样

每个划分的区间只需要快速支持这两个操作:

  1. 从中查询一个位置的值并删除
  2. 向末尾添加一个值

因为最多只有q个添加操作,q有3×1053 \times 10^53×105而一个序列原本就有3×1053 \times 10^53×105这么长,用高级数据结构显然太浪费,动态开点的线段树就可以搞定,两个操作一次时间复杂度都是log(n+q)log (n+q)log(n+q),新开的空间也是log(n+q)log (n+q)log(n+q),而一次查询2个或4个操作就可以搞定。

总时间复杂度:O(nlog(n+q))O( nlog(n+q) )O(nlog(n+q)),空间同

那么这个动态开点线段树怎么写呢:

把区间总范围设为1−(max(n,m)+q)1-(max(n,m)+q)1(max(n,m)+q),每个节点都设一个值cal表示节点区间包含的有效节点个数,叶节点再加一个val表示值,在这里,删除是指把此位置标为无效节点,并没有从序列中抹除,而2操作则是直接把最后一个有效点后的位置更新成要添加的值。

删除时直接在线段树上做二分,把叶节点cal改成0,上面的路径更新一下即可。

设一个Xi数组表示第i个序列的Xi位置还没有被插入过,每次插入时就可以精准定位,查完后Xi++,虽然不能保证和当前最后一个有效点紧挨着,但一定不会超出范围,这样效果是一样的。

下图展示了样例的第一个操作的过程:

有一步最为关键:如果一开始一个一个地更新初始方阵的每一个值,空间时间都要炸飞。可是不更新的话,前n或m-1个位置的值都不同,后q个位置又都是无效位置,没法操作。这时我们可以这样想,一个节点没有被建立,说明这个区间范围内没有被更新过,而我们知道节点所在序列,具体区间范围,又知道初始状态,那么我们就可以直接计算出有关这个节点的所有数据,而如果节点已被建立,直接使用节点数据即可。

还有就是此题卡常严重,非树状数组的童鞋们最好还是小心一点~~

代码:

#include <iostream>
#include <stdio.h>
#include <algorithm>
using namespace std;
const int N=3e5+5;
struct Node{
    Node* l;
    Node* r;
    int cal;
    long long val;
    Node(){
        cal=val=0;
        l=r=NULL;
    }
};
int n,m,q,Len,Xi[N];
Node* Root[N];

Node* NewNode(){
    return new Node();
}

inline int Cal(Node* o,int l,int r,int Limit)
{
    if(o!=NULL)
        return o->cal;
    r=min(Limit,r);
    return (l>r?0:r-l+1);
}

void update(Node* o,int l,int r,int k,long long val0,int Limit)
{
    o->cal++;
    if(l==r){
        o->val=val0;
        return;
    }
    int mid=(l+r)>>1,l_cal=Cal(o->l,l,mid,Limit);
    if(k<=mid)
    {
        if(o->l==NULL){
            o->l=NewNode();
            o->l->cal=l_cal;
        }
        update(o->l,l,mid,k,val0,Limit);
    }
    else
    {
        if(o->r==NULL){
            o->r=NewNode();
            o->r->cal=o->cal-1-l_cal;
        }
        update(o->r,mid+1,r,k,val0,Limit);
    }
}

long long query(Node* o,int l,int r,int k,int Limit,int id)
{
    o->cal--;
    if(l==r){
        if(!o->val)
        {
            long long id0=id,m0=m,l0=l;
            o->val=(id0?((id0-1ll)*m0+l0):(l0*m0));
        }
        return o->val;
    }
    int mid=(l+r)>>1,l_cal=Cal(o->l,l,mid,Limit);	
    if(k<=l_cal)
    {
        if(o->l==NULL){
            o->l=NewNode();
            o->l->cal=l_cal;
        }
        return query(o->l,l,mid,k,Limit,id);
    }
    else
    {
        if(o->r==NULL){
            o->r=NewNode();
            o->r->cal=o->cal+1-l_cal;
        }
        return query(o->r,mid+1,r,k-l_cal,Limit,id);
    }
}

int main()
{
    scanf("%d%d%d",&n,&m,&q);
    for(int i=0;i<=n;i++)
    {
        Root[i]=NewNode();
        Root[i]->cal=m-1;
        Xi[i]=m;
    }
    Root[0]->cal=n;
    Xi[0]=n+1;
    Len=max(n,m)+q;	
    while(q--)
    {
        int x,y;
        long long ans;
        scanf("%d%d",&x,&y);
        ans=query(Root[0],1,Len,x,n,0);
        if(y!=m)
        {
            long long ans1=query(Root[x],1,Len,y,m-1,x);
            update(Root[x],1,Len,Xi[x],ans,m-1);
            Xi[x]++;
            ans=ans1;
        }
        printf("%lld\n",ans);
        update(Root[0],1,Len,Xi[0],ans,n);
        Xi[0]++;
    }
    return 0;
}

### NOIP2017 提高组 列队 问题 解题思路 #### 问题描述 给定一个 n 行 m 列的矩阵,初始时每个位置都有一个人。有 q 次操作,每次操作给出两个人的位置 (x, y),表示这个人离开了队伍。离开的人会重新站在最后一列的最后一行。需要支持这些操作并能快速查询某个人当前所在的位置。 #### 数据结构选择 为了高效处理上述操作,可以采用动态开点线段树来管理行列中的人员变动情况[^2]。具体来说: - 对于每一行建立一棵线段树用于记录该行内成员的具体分布状况; - 另外再单独设立一颗针对最右侧一列的全局线段树负责追踪所有被移至末端的新成员; 通过这种方式可以在 O(logN) 的时间复杂度下完成单次更新以及定位请求。 #### 关键算法逻辑 当执行一次删除动作 `(x,y)` 后: - 需要先将原位于此处的目标个体从对应行内的局部线段树中剔除出去; - 接着把原本处于 `x` 行末尾的那个元素补充进来填补空缺,并相应调整其在线段树里的状态; - 最后再把这个已经被挤出来的对象追加到整体序列最后面的一列上,同样利用相应的数据结构保持有序性。 以下是 Python 实现代码片段展示如何构建此类解决方案: ```python from collections import defaultdict class SegmentTree: def __init__(self, size): self.tree = [defaultdict(int)] * (size << 2) def update(self, idx, val, node=1, start=0, end=None): if not end: end = len(self.tree) >> 2 if start == end: self.tree[node][idx] += val else: mid = (start + end) >> 1 if idx <= mid: self.update(idx, val, node << 1, start, mid) else: self.update(idx, val, (node << 1) | 1, mid + 1, end) self.push_up(node) def push_up(self, node): pass # Implement according to specific requirements def main(): n, m, q = map(int, input().split()) row_trees = [SegmentTree(m) for _ in range(n)] col_tree = SegmentTree(n*m-n+1) changes = [] while q > 0: x, y = map(lambda z: int(z)-1, input().split()) # Convert from 1-based index last_in_row = m - 1 removed_val = row_trees[x].query(y, y)[y] row_trees[x].update(y, -1) if y != last_in_row: moved_from_end_of_row = row_trees[x].query(last_in_row, last_in_row)[last_in_row] row_trees[x].update(last_in_row, -1) row_trees[x].update(y, moved_from_end_of_row) new_pos_at_last_col = col_tree.query(col_tree.size()-len(changes), col_tree.size())[col_tree.size()-len(changes)] col_tree.update(len(changes)+1, removed_val) changes.append((new_pos_at_last_col//m + 1, new_pos_at_last_col % m)) q -= 1 if __name__ == "__main__": main() ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值