loj#2048. 「HNOI2016」最小公倍数 ( JSOIP2022练习赛1 t1 )
给定一张 个顶点 条边的无向图(顶点编号为 ),每条边上带有权值。所有权值都可以分解成 的形式。
现在有 个询问,每次询问给定四个参数 、 、 和 ,请你求出是否存在一条顶点 到 之间的路径,使得路径依次经过的边上的权值的最小公倍数为 。
.
首先观察到如何判断最小公倍数是 .
考虑加入 的边,其中 , 此时会构成一个图,考虑图的联通情况用 维护 . 如果 之间存在最小公倍数是 的路径,必须要满足下面 3 个条件 。
- 联通 .
- 所在联通块 的幂次最大值恰好为 .
- 所在连通块 的幂次最大值恰好为 .
确定了使用 进行判断,但是出现了一个问题 和 有两个维度,如何用可行的复杂度控制当前图上的 大小 .
首先要控制 的大小,对边按照 排序,接着对边分块,每块的大小是 . 设当前块的 的最大值为 ,最小值为 .
现在,就是要处理出 的询问 . 因为我们**控制住了当前块的边数大小只有 **,所以,对于每个询问,相当于我们可以操作 条边. 此时,就需要保证 的大小顺序,将 从小到大递增 . 对于符合要求的询问按照 从小到大排序 .
在询问的同时按照 的大小依次扩展 的边 . 对于 来说, 的变化只会在 条边中 . 于此同时,我们还需要可撤销并查集来支持 条边的操作 .
对于块外 的边的复杂度是 的. 块内的边复杂度则是 的 .
时间复杂度 :
空间复杂度 :
当 的大小取到 的时候最快 .
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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通