[HNOI2016]最小公倍数

题目

  点这里看题目。

分析

  一类比较经典的分块优化暴力的思路。
  问题实际上是查询,当\(a\le Qa, b\le Qb\)的所有边都插入了图之后,\(u,v\)是否连通,并且\(u,v\)的连通块里面是否同时存在\(a=Qa\)\(b=Qb\)的边。
  以上信息可以用并查集来维护。
  问题的瓶颈是,如何快速地提取出需要的边。
  我们不妨考虑这样一个算法:
  将边按照\(a\)排序并且分块处理,块的大小是\(T\)。对于当前块\([i,i+T)\),我们处理\(Qa\)\([a_i,a_{i+T})\)之间的询问。
  现在考虑处理掉\(b\)。发现对于\([1,i)\)的边,当前询问只要求它\(b\le Qb\)。因此我们可以让\([1,i)\)的边按照\(b\)来排序,并且让所有询问按照\(Qb\)来排序。这样对于\([1,i)\)的边,\(b\)的限制就可以用指针方便地维护了。
  再考虑\([i,i+T)\)的边,由于它们的数量比较少,所以我们可以直接暴力插入合法边,查询完之后再删除。由于这里存在并查集的删除操作,因此我们需要用到可回退的并查集,配合秩优化。
  时间复杂度:
    多次排序:\(O(\frac{m^2}T\log_2m)\)
    块外边:\(O(\frac{m^2}T\log_2n)\)
    块内边:\(O(TQ\log_2n)\)
  总时间大概是......\(O(\frac{m^2}T\log_2n+\frac{mQ}T\log_2n)\)
  根据均值不等式可以得到,\(T=\sqrt{\frac{m^2}Q}\)是比较 OK 的吧......
  但是经过评测,\(T=\sqrt{m\log_2n}\)的时候是比较优秀的。

代码

#include <cmath>
#include <cstdio>
#include <algorithm>
using namespace std;

const int MAXN = 1e5 + 5, MAXM = 2e5 + 5;

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

template<typename _T>
void write( _T x )
{
	if( x < 0 ){ putchar( '-' ); x = ( ~ x ) + 1; }
	if( 9 < x ){ write( x / 10 ); }
	putchar( x % 10 + '0' );
}

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

template<typename _T>
void swapp( _T &x, _T &y )
{
	_T t = x; x = y, y = t;
}

struct element
{
	int u, v, a, b, s;
	element() { u = v = a = b = s = 0; }
	void get() { read( u ), read( v ), read( a ), read( b ); }
	element( const int U, const int V, const int A, const int B, const int S ) { u = U, v = V, a = A, b = B, s = S; }
}E[MAXM], q[MAXN], lst[MAXM];

int fa[MAXN], siz[MAXN], mxA[MAXN], mxB[MAXN];
int seq[MAXM];
int N, M, Q, blk, top;
bool ans[MAXN];

void upt( int &x, const int v ) { x = MAX( x, v ); }
bool cmp1( const element &x, const element &y ) { return x.a < y.a; }
bool cmp2( const element &x, const element &y ) { return x.b < y.b; }
int findSet( const int u ) { return u == fa[u] ? u : findSet( fa[u] ); }
void makeSet( const int n ) { for( int i = 1 ; i <= n ; i ++ ) fa[i] = i, siz[i] = 1, mxA[i] = mxB[i] = -1; }

void unionSet( int u, int v, const int A, const int B )
{
	u = findSet( u ), v = findSet( v );
	if( siz[u] > siz[v] ) swapp( u, v );
	lst[++ top] = element( u, v, mxA[v], mxB[v], siz[v] );
	if( u ^ v ) 
		fa[u] = v, siz[v] += siz[u], 
		upt( mxA[v], mxA[u] ), upt( mxB[v], mxB[u] );
	upt( mxA[v], A ), upt( mxB[v], B );
}

bool chk( int u, int v, const int A, const int B )
{
	u = findSet( u ), v = findSet( v );
	return u == v && mxA[u] == A && mxB[u] == B;
}

int main()
{
	read( N ), read( M );
	for( int i = 1 ; i <= M ; i ++ ) E[i].get();
	read( Q ), blk = sqrt( M * log2( N ) );
	for( int i = 1 ; i <= Q ; i ++ ) q[i].get(), q[i].s = i;
	sort( E + 1, E + 1 + M, cmp1 );
	sort( q + 1, q + 1 + Q, cmp2 );
	int tot = 0, p, cur;
	for( int i = 1 ; i <= M ; i += blk )
	{
		makeSet( N ), tot = 0;
		if( i > 1 ) std :: sort( E + 1, E + i, cmp2 );
		for( int k = 1 ; k <= Q ; k ++ )
			if( E[i].a <= q[k].a && ( q[k].a < E[i + blk].a || i + blk > M ) )
				seq[++ tot] = k;
		p = 1;
		for( int k = 1 ; k <= tot ; k ++ )
		{
			cur = seq[k];
			for( ; p < i && E[p].b <= q[cur].b ; p ++ ) 
				unionSet( E[p].u, E[p].v, E[p].a, E[p].b );
			top = 0;
			for( int j = i ; j < i + blk && j <= M ; j ++ )
				if( E[j].a <= q[cur].a && E[j].b <= q[cur].b )
					unionSet( E[j].u, E[j].v, E[j].a, E[j].b );
			ans[q[cur].s] = chk( q[cur].u, q[cur].v, q[cur].a, q[cur].b );
			while( top )
			{
				int u = lst[top].u, v = lst[top].v; fa[u] = u;
				mxA[v] = lst[top].a, mxB[v] = lst[top].b, siz[v] = lst[top].s;
				top --;
			}
		}
	}
	for( int i = 1 ; i <= Q ; i ++ ) puts( ans[i] ? "Yes" : "No" );
	return 0;
}
posted @ 2020-06-02 21:42  crashed  阅读(131)  评论(0编辑  收藏  举报