[HNOI2016]最小公倍数 (可回退并查集,回滚莫队)

题面

题目🔗链接

题目描述

给定一张 N N N 个顶点 M M M 条边的无向图(顶点编号为 1 , 2 , … , n 1,2,\ldots,n 1,2,,n),每条边上带有权值。所有权值都可以分解成 2 a × 3 b 2^a\times 3^b 2a×3b 的形式。

现在有 q q q 个询问,每次询问给定四个参数 u , v , a u,v,a u,v,a b b b,请你求出是否存在一条顶点 u u u v v v 之间的路径,使得路径依次经过的边上的权值的最小公倍数为 2 a × 3 b 2^a\times 3^b 2a×3b

注意:路径可以不是简单路径。

下面是一些可能有用的定义,如果与其它地方定义不同,在本题中以下面的定义为准:

路径:顶点序列 P  ⁣ : P 1 , P 2 , … , P k P \colon P_1,P_2,\ldots,P_k P:P1,P2,,Pk 是一条路径,当且仅当 k ≥ 2 k \geq 2 k2,且对于任意 1 ≤ i < k 1 \leq i < k 1i<k ,节点 P i P_i Pi P i + 1 P_{i+1} Pi+1 之间都有边相连。

输入格式

输入文件的第一行包含两个整数 N N N M M M,分别代表图的顶点数和边数。

接下来 M M M 行,每行包含四个整数 u , v , a , b u,v,a,b u,v,a,b 代表一条顶点 u u u v v v 之间、权值为 2 a × 3 b 2^a\times 3^b 2a×3b 的边。

接下来一行包含一个整数 q q q,代表询问数。

接下来 q q q 行,每行包含四个整数 u , v , a u,v,a u,v,a b b b,代表一次询问。询问内容请参见问题描述。

输出格式

对于每次询问,如果存在满足条件的路径,则输出一行 Yes,否则输出一行 No(注意:第一个字母大写,其余字母小写)。

输入输出样例

输入 #1

4 5
1 2 1 3
1 3 1 2
1 4 2 1
2 4 3 2
3 4 2 2
5
1 4 3 3
4 2 2 3
1 3 2 2
2 3 2 2
1 3 4 4

输出 #1

Yes 
Yes 
Yes 
No 
No

说明/提示

1 ≤ n , q ≤ 5 × 1 0 4 1\le n,q\le 5\times 10^4 1n,q5×104 1 ≤ m ≤ 1 0 5 1\leq m\leq 10^5 1m105 0 ≤ a , b ≤ 1 0 9 0\leq a,b\leq 10^9 0a,b109

题解

我们可以用这种办法判断是否满足条件:把权值为目标 l c m \rm lcm lcm 的因数——即边的 a , b a,b a,b 同时小于目标的 a , b a,b a,b——的边,保留下来。此时如果 u u u v v v 在一个连通块内,同时该连通块所有边的 l c m \rm lcm lcm 等于目标 l c m \rm lcm lcm ,那么就存在满足条件的路径。路径可以不是简单路径,所以,只要是在同一连通块的边都可以走一次。既然是维护连通块,不妨用并查集

那么,首先把 a a a b b b 离散化,因为有价值的仅仅是他们的相对大小关系。

然后,回滚莫队

我们把询问按照 a a a 分块,每个块内按照 b b b 从小到大排序。处理每个块的时候,并查集初始化,一条扫描线扫 b b b 端点。我们维护当前扫到的 b b b 的大小 R R R R R R 初始为 0 。当 R < b i R<b_i R<bi 的时候,R++ ,然后把 b = R , a ≤ 块 的 左 端 b=R,a\leq 块的左端 b=R,a 的边加进并查集,当 R R R 达到 b i b_i bi 时,记录此时并查集的状态。接着处理 a a a 端点,把 a ∈ ( 块 的 左 端 , a i ] , b ≤ b i a\in (块的左端,a_i],b\leq b_i a(,ai],bbi 的边加进并查集,查询该询问是否有解。最后把并查集退回到之前记录的状态,保留 R R R ,计算块内的下一个询问。

令块大小为 S S S ,那么时间应该是 q × S × α ( n ) + M S × M × α ( n ) ≥ ( 2 × ) M α ( n ) q q\times S\times\alpha(n)+\frac{M}{S}\times M\times\alpha(n)\geq(2\times)M\alpha(n)\sqrt{q} q×S×α(n)+SM×M×α(n)(2×)Mα(n)q ,当且仅当 S = M 2 q S=\sqrt{\frac{M^2}{q}} S=qM2 时取最小,最大数据下 S S S 可取 447 447 447

关于按 a a a 分块,有一个难点。那就是,当大量的边都有着同样的 a a a 抱团的时候,我们又不得不把所有的相同值的边都考虑,于是,直接按 a a a 的值分块,无疑会慢到极致。但是,我们会发现,设块的左端点为 l i l_i li ,所有 a = l i a=l_i a=li 的边只会被添加一次,是在扩展 R R R 的时候被添加的。所以,我们把边的数量 ≥ S \geq S S a a a 值单独算一个,就可以避免这个问题。

时间复杂度 O ( M α ( n ) q ) \rm O(M\alpha_{^{(n)}}\sqrt{q}) O(Mα(n)q ) ,跑上几百毫秒是可以的。

CODE

#include<set>
#include<map>
#include<stack>
#include<cmath>
#include<ctime>
#include<queue>
#include<bitset>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 400005
#define LL long long
#define DB double
#define ENDL putchar('\n')
#define lowbit(x) (-(x) & (x))
#define SQ 447
LL read() {
	LL f=1,x=0;char s = getchar();
	while(s < '0' || s > '9') {if(s=='-')f = -f;s = getchar();}
	while(s >= '0' && s <= '9') {x=x*10+(s-'0');s = getchar();}
	return f*x;
}
void putpos(LL x) {
	if(!x) return ;
	putpos(x/10); putchar('0'+(x%10));
}
void putnum(LL x) {
	if(!x) putchar('0');
	else if(x < 0) putchar('-'),putpos(-x);
	else putpos(x);
}
int n,m,s,o,k,Q;
int U[MAXN],V[MAXN],w2[MAXN],w3[MAXN];
int b1[MAXN],b2[MAXN],cn1,cn2,nm1,nm2;
map<int,int> mp1,mp2;
vector<int> bu[MAXN],rq[MAXN];
int bl[MAXN],br[MAXN],belong[MAXN],cnt;
struct it{
	int u,v,l,r,id;it(){u=v=l=r=id=0;}
	it(int U,int V,int A,int B,int I){u=U;v=V;l=A;r=B;id=I;}
	it(int U,int ma,int mb){u=U;l=ma;r=mb;v=0;id=0;}
}q[MAXN];
vector<it> B[MAXN];
int cnq;
bool as[MAXN];
bool cmp(it a,it b) {
	return a.r < b.r;
}
bool F_st;
stack<it> st;
int fa[MAXN],siz[MAXN],mx1[MAXN],mx2[MAXN];
int findf(int x) {return x==fa[x] ? x:(findf(fa[x]));}
void unionSet(int a,int b,int m2,int m3) {
	int u = findf(a),v = findf(b);
	if(siz[u] > siz[v]) swap(u,v),swap(a,b);
	if(u == v) {
		if(F_st)st.push(it(u,mx1[u],mx2[u]));
		mx1[u] = max(mx1[u],m2);
		mx2[u] = max(mx2[u],m3);
	}
	else {
		if(F_st)st.push(it(u,mx1[v],mx2[v]));
		siz[v] += siz[u]; fa[u] = v;
		mx1[v] = max(mx1[v],max(mx1[u],m2));
		mx2[v] = max(mx2[v],max(mx2[u],m3));
	}return ;
}
void Back(it t) {
	int u = t.u;
	if(fa[u] == u) {
		mx1[u] = t.l; mx2[u] = t.r;
	}
	else {
		int v = fa[u];
		mx1[v] = t.l; mx2[v] = t.r;
		siz[v] -= siz[u]; fa[u] = u;
	}return ;
}
void adda(int ad,int rr) {
	for(int i = 0;i < (int)bu[ad].size();i ++) {
		int x = bu[ad][i];
		if(w3[x] <= rr) {
			unionSet(U[x],V[x],w2[x],w3[x]);
		}
	}return ;
}
void addb(int ad,int rr) {
	for(int i = 0;i < (int)rq[ad].size();i ++) {
		int x = rq[ad][i];
		if(w2[x] <= rr) {
			unionSet(U[x],V[x],w2[x],w3[x]);
		}
	}return ;
}
void query(int x,int y,int &aa,int &bb) {
	aa = 0; bb = 0;
	if(findf(x) != findf(y)) return ;
	int v = findf(x);
	aa = mx1[v]; bb = mx2[v];
	return ;
}
int main() {
//	freopen("lcm.in","r",stdin);
//	freopen("lcm.out","w",stdout);
	n = read();m = read();
	for(int i = 1;i <= m;i ++) {
		U[i] = read();
		V[i] = read();
		w2[i] = read();
		w3[i] = read();
		b1[++ cn1] = w2[i];
		b2[++ cn2] = w3[i];
	}
	sort(b1 + 1,b1 + 1 + m);
	sort(b2 + 1,b2 + 1 + m);
	for(int i = 1;i <= m;i ++) {
		if(i == 1 || b1[i] > b1[i-1]) mp1[b1[i]] = ++ nm1;
		if(i == 1 || b2[i] > b2[i-1]) mp2[b2[i]] = ++ nm2;
	}
	for(int i = 1;i <= m;i ++) {
		w2[i] = mp1[w2[i]]; w3[i] = mp2[w3[i]];
		bu[w2[i]].push_back(i);
		rq[w3[i]].push_back(i);
	}
	int ct = 0,prl = 0;
	for(int i = 1;i <= nm1;i ++) {
		ct += bu[i].size();
		belong[i] = cnt + 1;
		if(ct >= SQ || i == nm1 || (int)bu[i+1].size() >= SQ) {
			cnt ++;
			bl[cnt] = prl + 1;
			prl = br[cnt] = i;
			ct = 0;
		}
	}
	Q = read();
	for(int i = 1;i <= Q;i ++) {
		s = read();o = read();
		int aa = mp1[read()],bb = mp2[read()];
		if(aa && bb) {
			it t = it(s,o,aa,bb,i);
			q[++ cnq] = t;
			B[belong[t.l]].push_back(t);
		}
	}
	for(int i = 1;i <= cnt;i ++) {
		sort(B[i].begin(),B[i].end(),cmp);
		for(int j = 1;j <= n;j ++) {
			fa[j] = j; siz[j] = 1;
			mx1[j] = 0; mx2[j] = 0;
		}
		while(!st.empty()) st.pop();
		int R = 0;
		for(int j = 0;j < (int)B[i].size();j ++) {
			it x = B[i][j];
			while(R < x.r) addb(++ R,bl[i]);
			int L = bl[i];
			F_st = 1;
			while(L < x.l) adda(++ L,x.r);
			F_st = 0;
			int sa,sb; query(x.u,x.v,sa,sb);
			as[x.id] = (sa == x.l && sb == x.r);
			while(!st.empty()) {
				it t = st.top();st.pop();
				Back(t);
			}
		}
	}
	for(int i = 1;i <= Q;i ++) {
		printf(as[i] ? "Yes\n":"No\n");
	}
	return 0;
}
/*
q*S*log + M/S*M*log >= 2*sqrt{q}*M*log
q*S = M/S*M => S = sqrt(M*M/q) = sqrt(2e5) = 447
*/
posted @ 2021-08-15 10:53  DD_XYX  阅读(51)  评论(0编辑  收藏  举报