loj#2048. 「HNOI2016」最小公倍数 ( JSOIP2022练习赛1 t1 )

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

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

\(1\leq n,q\leq 5\cdots 10^4,1\leq m\leq 10^5,0\leq a,b\leq 10^9\) .

首先观察到如何判断最小公倍数是 \(2^a\cdot 3^b\) .

考虑加入 \(2^{a_0}\cdot 3^{b_0}\) 的边,其中 \(a_0\leq a,b_0\leq b\) , 此时会构成一个图,考虑图的联通情况用 \(\rm{dsu}\) 维护 . 如果 \(u,v\) 之间存在最小公倍数是 \(2^a\cdot 3^b\) 的路径,必须要满足下面 3 个条件 。

  1. \(u,v\) 联通 .
  2. \(u,v\) 所在联通块 \(2\) 的幂次最大值恰好为 \(a\) .
  3. \(u,v\) 所在连通块 \(3\) 的幂次最大值恰好为 \(b\) .

确定了使用 \(\rm{dsu}\) 进行判断,但是出现了一个问题 \(a\)\(b\) 有两个维度,如何用可行的复杂度控制当前图上的 \(a,b\) 大小 .

首先要控制 \(a\) 的大小,对边按照 \(a\) 排序,接着对边分块,每块的大小是 \(S\) . 设当前块的 \(a\) 的最大值为 \(mx\) ,最小值为 \(mn\) .

现在,就是要处理出 \(mn\leq a_0<mx\) 的询问 . 因为我们**控制住了当前块的边数大小只有 \(S\) **,所以,对于每个询问,相当于我们可以操作 \(O(S)\) 条边. 此时,就需要保证 \(b\) 的大小顺序,将 \(b\) 从小到大递增 . 对于符合要求的询问按照 \(b\) 从小到大排序 .

在询问的同时按照 \(b\) 的大小依次扩展 \(a<mn\) 的边 . 对于 \(a\) 来说,\(a\) 的变化只会在 \(S\) 条边中 . 于此同时,我们还需要可撤销并查集来支持 \(S\) 条边的操作 .

对于块外 \(a<mn\) 的边的复杂度是 \(O(\frac{m}{S}m\alpha(n))\) 的. 块内的边复杂度则是 \(O(qS\alpha(n))\) 的 .

时间复杂度 : \(O(\frac{m}{S}m\alpha(n)+qS\alpha(n))\)

空间复杂度 : \(O(n+m)\)

\(S\) 的大小取到 \(800\) 的时候最快 .

code

#include<bits/stdc++.h>
using namespace std;
char in[100005];
int iiter=0,llen=0;
inline char get(){
	if(iiter==llen)llen=fread(in,1,100000,stdin),iiter=0;
	if(llen==0)return EOF;
	return in[iiter++];
}
inline int rd(){
	char ch=get();while(ch<'0'||ch>'9')ch=get();
	int res=0;while(ch>='0'&&ch<='9')res=(res<<3)+(res<<1)+ch-'0',ch=get();
	return res;
}
inline void pr(int res){
	if(res==0){putchar('0');return;}
	static int out[10];int len=0;
	while(res)out[len++]=res%10,res/=10;
	for(int i=len-1;i>=0;i--)putchar(out[i]+'0');
}
const int N=5e4+10,M=1e5+10;
const int B=810;
int n,m,q;
class edge{public:int u,v,a,b;}e[M];
class query{public:int u,v,a,b,id;}qr[N];
bool ans[N],ok[N];
inline bool cmpa(const edge&A,const edge&B){return A.a<B.a;}
inline bool cmpb(const edge&A,const edge&B){return A.b<B.b;}
inline bool cmpb2(const query&A,const query&B){return A.b<B.b;}
int fa[N],sz[N],x2[N],x3[N];
edge s[M];
int top=0;
inline void init(){
	for(int i=0;i<n;i++)fa[i]=i,sz[i]=1;
	memset(x2,-1,sizeof(x2));
	memset(x3,-1,sizeof(x3));
	top=0;
}
inline int get_fa(int x){
	return fa[x]==x?x:get_fa(fa[x]);
}
inline void ins(int u,int v,int a,int b,bool tp){
	u=get_fa(u);v=get_fa(v);
	if(sz[v]>sz[u])swap(u,v);
	if(!tp){
		x2[u]=max(x2[u],a);x3[u]=max(x3[u],b);
		if(u==v)return;
		fa[v]=u;sz[u]+=sz[v];
		x2[u]=max(x2[u],x2[v]);x3[u]=max(x3[u],x3[v]);
	}else{
		s[++top]=(edge){u,u!=v?v:-1,x2[u],x3[u]}; 
		x2[u]=max(x2[u],a);x3[u]=max(x3[u],b);
		if(u==v)return;
		fa[v]=u;sz[u]+=sz[v];
		x2[u]=max(x2[u],x2[v]);x3[u]=max(x3[u],x3[v]);
	}
}
void undo(){
	while(top){
		int u=s[top].u,v=s[top].v,a=s[top].a,b=s[top].b;
		x2[u]=a;x3[u]=b;
		if(v!=-1)fa[v]=v,sz[u]-=sz[v];
		top--;
	}
}
inline int qry(int u,int v,int a,int b){
	u=get_fa(u);v=get_fa(v);
	if(u!=v)return false;
	return x2[u]==a&&x3[u]==b;
}
int main(){
	n=rd();m=rd();
	for(int i=0;i<m;i++)e[i].u=rd()-1,e[i].v=rd()-1,e[i].a=rd(),e[i].b=rd();
	q=rd();
	for(int i=0;i<q;i++)
		qr[i].u=rd()-1,qr[i].v=rd()-1,qr[i].a=rd(),qr[i].b=rd(),qr[i].id=i;
	sort(e,e+m,cmpa);e[m].a=1e9+10;
	sort(qr,qr+q,cmpb2);
	for(int id=0;id<=(m-1)/B;id++){
		int l=id*B,r=min(m-1,l+B-1);
		if(l>r)break;
		vector<query>ve;
		for(int i=0;i<q;i++)if(e[l].a<=qr[i].a&&qr[i].a<e[r+1].a)ve.push_back(qr[i]);
		sort(e,e+l,cmpb);
		int p=0;init();
		for(int i=0;i<(int)ve.size();i++){
			while(p<l&&e[p].b<=ve[i].b)ins(e[p].u,e[p].v,e[p].a,e[p].b,0),p++;
			for(int j=l;j<=r;j++)if(e[j].a<=ve[i].a&&e[j].b<=ve[i].b)
				ins(e[j].u,e[j].v,e[j].a,e[j].b,1);
			ans[ve[i].id]=qry(ve[i].u,ve[i].v,ve[i].a,ve[i].b);
			undo();
		}
	}
	for(int i=0;i<q;i++){
		if(ans[i])putchar('Y'),putchar('e'),putchar('s');
		else putchar('N'),putchar('o');
		putchar('\n');
	}
	return 0;
}
posted @ 2022-02-05 18:55  xyangh  阅读(25)  评论(0)    收藏  举报