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

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

现在有 q 个询问,每次询问给定四个参数 uvab ,请你求出是否存在一条顶点 uv 之间的路径,使得路径依次经过的边上的权值的最小公倍数为 2a3b

1n,q5104,1m105,0a,b109 .

首先观察到如何判断最小公倍数是 2a3b .

考虑加入 2a03b0 的边,其中 a0a,b0b , 此时会构成一个图,考虑图的联通情况用 dsu 维护 . 如果 u,v 之间存在最小公倍数是 2a3b 的路径,必须要满足下面 3 个条件 。

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

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

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

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

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

对于块外 a<mn 的边的复杂度是 O(mSmα(n)) 的. 块内的边复杂度则是 O(qSα(n)) 的 .

时间复杂度 : O(mSmα(n)+qSα(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 @   xyangh  阅读(21)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示