[HNOI2016]最小公倍数
不难发现题意就是,每条边有两种权值,每次询问两个点\(u,v\),问\(u\)到\(v\)是否存在一条路径满足第一类边权的最大值为\(a\),第二类边权的最大值为\(b\)
一个直观的暴力做法就是把\(a_i\leq a,b_i\leq b\)的边都加进来,看看加入这些边后\(u,v\)是否联通;如果联通,在看看\(u,v\)所在联通块的两种边权的最大值是否分别为\(a、b\);如果是,答案为Yes
,否则为No
。显然这个暴力可以使用并查集来实现。
有了暴力现在开始分块,我们先将所有边按照第一类权值排序,询问按照第二类权值排序;之后分块,对于每个块我们维护一个前缀并查集,我们在处理一组询问之前会将所有第二类权值不超过这个询问的边加入这些前缀并查集中,这样我们的询问就只是查一个前缀,我们暴力把散块里的边加入到上一个并查集里就好了,询问完了再一条一条撤销回来
时间复杂度是\(O((m+q)\sqrt{m}\log n)\),不太会将\(\log\)放进根号里面;经过手玩,块大小在\(5\sqrt{m}\)的时候跑得最快
代码
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include<bits/stdc++.h>
#define re register
const int maxn=5e4+5;
const int M=1605;
inline int read() {
char c=getchar();int x=0;while(c<'0'||c>'9')c=getchar();
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-48,c=getchar();return x;
}
struct edge{unsigned short u,v;int x,y;}a[maxn<<1];
struct Ask{unsigned short s,t,rk;int x,y;}q[maxn];
int m,B,p[maxn<<1],id[maxn<<1],vx[maxn<<1],vy[maxn<<1],cnt,num,l[M],r[M];
unsigned short n,Q,Ans[maxn];
inline int max(int a,int b) {return a>b?a:b;}
struct Dsu {
int C[M],D[M],top,va[maxn],vb[maxn];
unsigned short tp,st[M];
unsigned short A[M],sz[maxn],B[M],fa[maxn],f[M];
std::bitset<M> vis;
inline int find(int x) {
for(;x!=fa[x];x=fa[x]);return x;
}
inline int Find(int x) {
return x==fa[x]?x:fa[x]=Find(fa[x]);
}
inline void Merge(const int &x,const int &y,const int &a,const int &b) {
int xx=Find(x),yy=Find(y);
if(xx==yy) {
va[xx]=max(va[xx],a);vb[xx]=max(vb[xx],b);
return;
}
if(sz[xx]<sz[yy])std::swap(xx,yy);
if(sz[xx]==sz[yy]) sz[xx]++;fa[yy]=xx;
va[xx]=max(va[xx],max(va[yy],a));
vb[xx]=max(vb[xx],max(vb[yy],b));
}
inline void merge(const int &x,const int &y,const int &a,const int &b) {
int xx=find(x),yy=find(y);
if(xx==yy) {
vis[++top]=0;A[top]=xx;
C[top]=va[xx],D[top]=vb[xx];
va[xx]=max(va[xx],a),vb[xx]=max(vb[xx],b);
return;
}
if(sz[xx]<sz[yy]) std::swap(xx,yy);
vis[++top]=1;
A[top]=xx,B[top]=yy;fa[yy]=xx;
if(sz[xx]==sz[yy]) sz[xx]++,f[top]=1;else f[top]=0;
C[top]=va[xx],D[top]=vb[xx];
va[xx]=max(va[xx],max(va[yy],a));
vb[xx]=max(vb[xx],max(vb[yy],b));
}
inline void back() {
if(vis[top]) fa[B[top]]=B[top];
sz[A[top]]-=f[top];
va[A[top]]=C[top],vb[A[top]]=D[top];
--top;
}
inline int query(const int &x,const int &y,const int &a,const int &b) {
int xx=find(x),yy=find(y);
if(xx!=yy) return 0;
return va[xx]==a&&vb[xx]==b;
}
inline void build() {for(re int i=1;i<=n;i++) fa[i]=i,sz[i]=1,va[i]=vb[i]=-1;}
}G[67];
inline int cmp(const edge &A,const edge &B) {return A.x<B.x;}
inline int cop(int A,int B) {return a[A].y<a[B].y;}
inline int cxp(const Ask &A,const Ask &B) {return A.y<B.y;}
inline int ask(int *c,int t) {
int l=1,r=m,nw=0;
while(l<=r) {
int mid=l+r>>1;
if(c[mid]==t) nw=mid;
if(c[mid]<=t) l=mid+1;else r=mid-1;
}
return nw;
}
inline void add(int t) {
for(re int i=p[t];i<=num;++i) G[i].Merge(a[t].u,a[t].v,a[t].x,a[t].y);
}
int main() {
n=read(),m=read();B=5*std::sqrt(m);
for(re int i=1;i<=m;i++)a[i].u=read(),a[i].v=read(),a[i].x=read(),a[i].y=read();
std::sort(a+1,a+m+1,cmp);
for(re int i=1;i<=m;i++)id[i]=i;
std::sort(id+1,id+m+1,cop);Q=read();
for(re int i=1;i<=m;i++) vx[i]=a[i].x;
for(re int i=1;i<=m;i++) vy[i]=a[id[i]].y;
for(re int i=1;i<=Q;++i) {
q[++cnt].s=read(),q[cnt].t=read(),q[cnt].x=read(),q[cnt].y=read();
if(ask(vx,q[cnt].x)==0||ask(vy,q[cnt].y)==0) {
--cnt;continue;
}
q[cnt].rk=i;
}
int L=1,R;G[0].build();
while(L<=m) {
R=L+B-1;if(R>m) R=m;++num;
for(re int i=L;i<=R;i++) p[i]=num;
l[num]=L,r[num]=R;G[num].build();L=R+1;
}
std::sort(q+1,q+cnt+1,cxp);int lp=1;
for(re int i=1;i<=cnt;i++) {
while(lp<=m&&vy[lp]<=q[i].y) add(id[lp]),lp++;
int pos=ask(vx,q[i].x);
if(p[pos+1]!=p[pos]) {
Ans[q[i].rk]=G[p[pos]].query(q[i].s,q[i].t,q[i].x,q[i].y);
continue;
}
int u=p[pos]-1,sth=0;
for(re int j=l[u+1];j<=pos;j++)
if(a[j].y<=q[i].y) ++sth,G[u].merge(a[j].u,a[j].v,a[j].x,a[j].y);
Ans[q[i].rk]=G[u].query(q[i].s,q[i].t,q[i].x,q[i].y);
while(sth--) G[u].back();
}
for(re int i=1;i<=Q;i++) puts(Ans[i]?"Yes":"No");return 0;
}