题目
点这里看题目。
分析
观察一下部分分,前三个 subtasks 都比较简单。
仔细思考一下,发现之后的难点都在于 \(x,y\) 两个坐标分离处理,这导致我们无法轻易地找出需要被修改的点。不过,一旦找出了需要被修改的点,我们反而可以直接暴力修改,只不过需要保证相同坐标的点在合并过程中不被拆开,这样复杂度才是对的。
再思考一下,操作这么难受,主要原因是图形太奇怪了。如果整个图形是一个矩形/正方形,那么 \(x,y\) 两个坐标就可以分离处理。为了达成这一点,我们考虑一种常见的策略——分治:
具体的思路是,我们每次选取一个右上角在直线 \(x+y=N\) 上的矩形,处理在这一局部中的所有操作。处理完之后,我们可能需要递归到子三角形中,并继续进行划分。于是我们看到了如上图所示的划分形态——蓝色表示第一层、红色表示第二层、绿色表示第三层......
那么,粗糙地看,我们在一个分治矩形中需要做的就是快速地处理操作,以求在这一系列操作结束后,确定所有点的去向(要么是到某个子三角形中,要么是留在矩形内),并且相应地处理询问。由于所有的操作都有较严格的时间顺序,并且这样的分治结构并不能帮助我们调整这一顺序,我们需要按照时间扫描所有的操作——换言之,“在线”处理。
在一个分治矩形中,我们主要关注 H,V 两种操作的执行逻辑。以 H 操作为例,假设矩形的长为 \(x\),宽为 \(N-x\),扫帚的长度为 \(l\),那么:
-
如果 \(l<N-x\),那么对于矩形内的操作相当于是把 \(y\le l\) 的点全部推到右侧的子三角形上。为了维护这个操作,我们需要能够将点按照 \(y\) 从小到大取出,用一个堆即可。
这一类操作之后只会影响右侧三角形的点,所以它只需要下放到右侧三角形处理。
-
如果 \(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;
}