「JOISC2020」扫除

题目

点这里看题目。

分析

观察一下部分分,前三个 subtasks 都比较简单。

仔细思考一下,发现之后的难点都在于 \(x,y\) 两个坐标分离处理,这导致我们无法轻易地找出需要被修改的点。不过,一旦找出了需要被修改的点,我们反而可以直接暴力修改,只不过需要保证相同坐标的点在合并过程中不被拆开,这样复杂度才是对的。

再思考一下,操作这么难受,主要原因是图形太奇怪了。如果整个图形是一个矩形/正方形,那么 \(x,y\) 两个坐标就可以分离处理。为了达成这一点,我们考虑一种常见的策略——分治

具体的思路是,我们每次选取一个右上角在直线 \(x+y=N\) 上的矩形,处理在这一局部中的所有操作。处理完之后,我们可能需要递归到子三角形中,并继续进行划分。于是我们看到了如上图所示的划分形态——蓝色表示第一层、红色表示第二层、绿色表示第三层......

那么,粗糙地看,我们在一个分治矩形中需要做的就是快速地处理操作,以求在这一系列操作结束后,确定所有点的去向(要么是到某个子三角形中,要么是留在矩形内),并且相应地处理询问。由于所有的操作都有较严格的时间顺序,并且这样的分治结构并不能帮助我们调整这一顺序,我们需要按照时间扫描所有的操作——换言之,“在线”处理。


在一个分治矩形中,我们主要关注 H,V 两种操作的执行逻辑。以 H 操作为例,假设矩形的长为 \(x\),宽为 \(N-x\),扫帚的长度为 \(l\),那么:

  1. 如果 \(l<N-x\),那么对于矩形内的操作相当于是把 \(y\le l\) 的点全部推到右侧的子三角形上。为了维护这个操作,我们需要能够将点按照 \(y\) 从小到大取出,用一个堆即可。

    这一类操作之后只会影响右侧三角形的点,所以它只需要下放到右侧三角形处理。

  2. 如果 \(l\ge N-x\),那么相当于将所有 \(x<N-l\) 的点的 \(x\) 坐标改成 \(N-l\)。这个操作也可以用堆维护:暴力地弹出 \(x<N-l\) 的点,修改之后放回堆。为了保证复杂度,我们需要这些点全部合在一起,作为一个等价类处理。幸运的是,复杂度分析说明我们不需要维护全局的等价类,只需要在每个分治矩形的局部维护这样的等价类。

    这一类操作之后只会影响上侧三角形的点,所以它只需要下放到上侧三角形处理。

V 操作也类似。这是一部分,对于剩下的“点”和“询问”,执行逻辑是:

  • 点:如果遇到了第一类 H 操作,我们需要将取出来的 \(y\le l\) 的点全部推送到右侧子三角形中,留着之后处理;如果遇到了第二类 H 操作,我们可以直接维护好坐标。

    而对于 V 操作的处理方式完全对称。

  • 询问:因为操作是按照时间扫描的,所以扫描到询问时我们可以知道对应点是否还在矩形内。如果在,我们可以解决它;否则,递归到对应的子三角形处理。

堆里面维护的是等价类,我们还需要能够枚举等价类中的所有元素,并且支持快速合并——用自顶向下的树形结构维护即可。在询问的时候,我们需要能够快速查询某一个等价类的坐标值,这个可以直接并查集维护。

所以,如果某一层分治矩形中一共有 \(c\) 个操作,这一层的复杂度即为 \(O(c\log c)\)


全局的细节:虽然我们要求按照时间顺序处理,但是同一层的子三角形是完全独立的,它们之间没有时间的羁绊,所以可以分别处理。

对于任何一个操作,它至多出现在 \(O(\log n)\) 个分治矩形中,所以复杂度实际上为 \(O((m+q)\log (m+q)\log n)\),空间是线性的。看起来常数比较大,但是跑起来还是非常之优秀的。

另外,我们实际上是在全局中维护了一个分治树的结构,并且根据操作的性质,按树的遍历顺序而非时间处理所有操作,所以这个分治算法可以倒回去还原出一个在线的算法,唯一的问题就是空间会增大到 \(O((m+q)\log (m+q))\)

代码

完整代码
#include <queue>
#include <cstdio>
#include <vector>
#include <utility>

#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )

const int MAXM = 1.5e6 + 5;

template<typename _T>
inline void Read( _T &x ) {
    x = 0; char s = getchar(); bool f = false;
    while( s < '0' || '9' < s ) { f = s == '-', s = getchar(); }
    while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
    if( f ) x = -x;
}

template<typename _T>
inline void Write( _T x ) {
    if( x < 0 ) putchar( '-' ), x = -x;
    if( 9 < x ) Write( x / 10 );
    putchar( x % 10 + '0' );
}

template<typename _T>
inline _T Max( const _T &a, const _T &b ) {
    return a > b ? a : b;
}

struct Operation {
    int opt, arg1, arg2, id;

    Operation(): opt(), arg1(), arg2(), id( 0 ) {}
    Operation( int O, int A1, int A2, int I ): opt( O ), arg1( A1 ), arg2( A2 ), id( I ) {}
};

struct Point {
    int x, y, id;

    Point(): x( 0 ), y( 0 ), id( 0 ) {}
    Point( int X, int Y, int I ): x( X ), y( Y ), id( I ) {}
};

struct PointCompByX {
    inline bool operator () ( const Point &a, const Point &b ) const {
        return ! ( a.x < b.x );
    }
};

struct PointCompByY {
    inline bool operator () ( const Point &a, const Point &b ) const {
        return ! ( a.y < b.y );
    }
};

struct UFS {
    int fa[MAXM];

    UFS(): fa{} {}

    inline void MakeSet( const int &n ) {
        rep( i, 1, n ) fa[i] = i;
    }

    int FindSet( const int &u ) {
        return fa[u] == u ? u : ( fa[u] = FindSet( fa[u] ) );
    }

    inline void UnionSet( const int &u, const int &v ) {
        fa[FindSet( u )] = FindSet( v );
    }
};

UFS faX, faY;

std :: priority_queue<Point, std :: vector<Point>, PointCompByX> srtByX;
std :: priority_queue<Point, std :: vector<Point>, PointCompByY> srtByY;
std :: vector<Operation> ini; 
std :: vector<int> sonX[MAXM], sonY[MAXM];

int q[MAXM];

bool imp[MAXM];

int extc[MAXM];
int ansX[MAXM], ansY[MAXM];
int posX[MAXM], posY[MAXM];

int N, M, Q;

inline void Read( Operation &op ) {
    Read( op.opt ), Read( op.arg1 );
    if( op.opt == 4 ) Read( op.arg2 );
}

void Divide( const int &sL, const int &sR, std :: vector<Operation> &glb ) {
    if( sL > sR || glb.empty() ) return ;
    if( sL == sR ) {
        for( const auto &x : glb ) if( x.opt == 1 )
            ansX[x.id] = sL, ansY[x.id] = N - sL;
        return ;
    }
    bool any = false;
    for( const auto &x : glb )
        if( x.opt == 1 ) {
            any = true; break;
        }
    if( ! any ) return ;
    std :: vector<Operation> sub[2];
    int mid = ( sL + sR ) >> 1;
    // use potential to handle merging
    while( ! srtByX.empty() ) srtByX.pop();
    while( ! srtByY.empty() ) srtByY.pop();
    for( const auto &x : glb ) {
        if( x.opt == 1 ) {
            if( ~ extc[x.arg1] )
                sub[extc[x.arg1]].push_back( x );
            else
                ansX[x.id] = posX[faX.FindSet( x.arg1 )],
                ansY[x.id] = posY[faY.FindSet( x.arg1 )];
        }
        if( x.opt == 2 ) {
            if( x.arg1 < N - mid ) {
                // push out
                Point tmp;
                while( ! srtByY.empty() ) {
                    tmp = srtByY.top();
                    if( tmp.y > x.arg1 ) break;
                    srtByY.pop();
                    int h = 1, t = 0; q[++ t] = tmp.id;
                    while( h <= t ) {
                        int u = q[h ++];
                        if( extc[u] == -1 )
                            sub[extc[u] = 1].push_back( Operation( 4, mid + 1, posY[tmp.id], u ) );
                        for( const int &x : sonY[u] ) q[++ t] = x;
                        std :: vector<int>().swap( sonY[u] );
                    }
                }
                sub[1].push_back( x );
            } else {
                // add a tag on
                if( x.arg1 > N - mid ) sub[0].push_back( x );
                int prev = 0; Point lst;
                while( ! srtByX.empty() && srtByX.top().x <= N - x.arg1 ) {
                    lst = srtByX.top(), srtByX.pop();
                    posX[lst.id] = lst.x = N - x.arg1;
                    if( prev ) 
                        faX.UnionSet( prev, lst.id ), 
                        sonX[lst.id].push_back( prev );
                    prev = lst.id;
                }
                if( lst.id ) srtByX.push( lst );
            }
        }
        if( x.opt == 3 ) {
            if( x.arg1 < mid ) {
                // push out
                Point tmp;
                while( ! srtByX.empty() ) {
                    tmp = srtByX.top();
                    if( tmp.x > x.arg1 ) break;
                    srtByX.pop();
                    int h = 1, t = 0; q[++ t] = tmp.id;
                    while( h <= t ) {
                        int u = q[h ++];
                        if( extc[u] == -1 )
                            sub[extc[u] = 0].push_back( Operation( 4, posX[tmp.id], N - mid + 1, u ) );
                        for( const int &x : sonX[u] ) q[++ t] = x;
                        std :: vector<int>().swap( sonX[u] );
                    }
                }
                sub[0].push_back( x );
            } else {
                // add a tag on
                if( x.arg1 > mid ) sub[1].push_back( x );
                int prev = 0; Point lst;
                while( ! srtByY.empty() && srtByY.top().y <= N - x.arg1 ) {
                    lst = srtByY.top(), srtByY.pop();
                    posY[lst.id] = lst.y = N - x.arg1;
                    if( prev ) 
                        faY.UnionSet( prev, lst.id ),
                        sonY[lst.id].push_back( prev );
                    prev = lst.id;
                }
                if( lst.id ) srtByY.push( lst );
            }
        }
        if( x.opt == 4 ) {
            if( x.arg2 > N - mid ) {
                sub[extc[x.id] = 0].push_back( x ); continue;
            }
            if( x.arg1 > mid ) {
                sub[extc[x.id] = 1].push_back( x ); continue;
            }
            sonX[x.id].clear(), sonY[x.id].clear();
            faX.fa[x.id] = x.id, faY.fa[x.id] = x.id;
            extc[x.id] = -1, posX[x.id] = x.arg1, posY[x.id] = x.arg2;
            srtByX.push( Point( posX[x.id], posY[x.id], x.id ) );
            srtByY.push( Point( posX[x.id], posY[x.id], x.id ) );
        }
    }
    std :: vector<Operation> ().swap( glb );
    Divide( sL, mid - 1, sub[0] );
    Divide( mid + 1, sR, sub[1] );
}

int main() {
    Read( N ), Read( M ), Read( Q ), ini.reserve( M + Q );
    rep( i, 1, M ) {
        int x, y; Read( x ), Read( y );
        ini.push_back( Operation( 4, x, y, i ) );
    }
    rep( i, 1, Q ) {
        Operation tmp; Read( tmp );
        tmp.id = tmp.opt == 4 ? ++ M : i;
        ini.push_back( tmp ), imp[i] = tmp.opt == 1;
    }
    Divide( 0, N, ini );
    rep( i, 1, Q ) if( imp[i] )
        Write( ansX[i] ), putchar( ' ' ), Write( ansY[i] ), putchar( '\n' );
    return 0;
}
posted @ 2023-01-12 08:38  crashed  阅读(144)  评论(1编辑  收藏  举报