先考虑如果只有一个限制该怎么做。
一个简单的思路就是离线下来,然后排序过后双指针扫描加边和处理询问即可,用并查集维护。
或者每次询问的时候都暴力遍历所有边然后并查集。
那么现在有了两个限制,单独并不好做
考虑优化这个过程,我们可以先按 (a) 排序然后把询问分块,然后相当于对于一个块之前的所有边的 (a) 的相对大小都是固定下来的,那么对于这些边我们可以直接考虑对当前的块中询问做双指针,然后对于当前块的边,我们在每次询问的时候都枚举一遍所有边然后加入即可。
每次操作完了之后要记得把当前的块中的边按 (a) 排序过后归并进边集合内。
这需要可撤销并查集来维护,时间复杂度是 (O(nsqrt{n}logn)) 。
还可以继续优化。
发现对于块内的边,我们可以直接BFS一遍,然后找到路径最大值就可以回答询问了,这里可以使用路径压缩按秩合并并查集,时间复杂度 (O(nsqrt{n}alpha(n))) 。
同时,这道题的边要求其实就是一个二维偏序的要求,而我们想一下线段树分治的要求呢?——一维偏序。
那么我们其实可以考虑使用 (KD-Tree) 套用线段树分治的思路,使用 (KD-Tree) 分治同样也可以解决这个问题,使用可撤销并查集维护,时间复杂度 (O(nlogn)) 。
代码:(可撤销并查集+分块)
#include<bits/stdc++.h>
using namespace std;
template <typename T>
inline void read(T &x){
x=0;char ch=getchar();bool f=false;
while(!isdigit(ch)){if(ch=='-'){f=true;}ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x=f?-x:x;
return ;
}
template <typename T>
inline void write(T x){
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10^48);
return ;
}
const int N=5e4+5,M=1e5+5,Q=1e3;
struct line{
int s,t,a,b,ans,no;
friend bool operator < (line x,line y){
return x.a<y.a;
}
}l[M],l2[M],q[N];
inline bool cmp1(line x,line y){return x.a<y.a;}
inline bool cmp2(line x,line y){return x.b<y.b;}
inline bool cmp3(line x,line y){return x.no<y.no;}
int n,m,K,block[Q];
struct SIT{int type,x,num1,num2;}sta[N];
int top;
struct Unf{
int fa[N],Siz[N],Maxa[N],Maxb[N];
int FindFather(int x){
if(fa[x]==0) return x;
return FindFather(fa[x]);
}
void Merge(int x,int y,int a,int b,bool type){
int fa1=FindFather(x),fa2=FindFather(y);
if(Siz[fa1]>Siz[fa2]) swap(x,y),swap(fa1,fa2);
if(type==1) sta[++top].type=1,sta[top].x=fa2,sta[top].num1=Maxa[fa2],sta[top].num2=Maxb[fa2];
Maxa[fa2]=max(max(Maxa[fa2],Maxa[fa1]),a);
Maxb[fa2]=max(max(Maxb[fa2],Maxb[fa1]),b);
if(fa1==fa2) return;
if(type==1) sta[++top].type=0,sta[top].x=fa1,sta[top].num1=fa2,sta[top].num2=Siz[fa1];
fa[fa1]=fa2,Siz[fa2]+=Siz[fa1];
}
void Return(){
for(;top>0;top--){
if(sta[top].type==0) fa[sta[top].x]=0,Siz[sta[top].num1]-=sta[top].num2;
else Maxa[sta[top].x]=sta[top].num1,Maxb[sta[top].x]=sta[top].num2;
}
}
int Query(int x,int y,int a,int b){
if(x==y&&a==0&&b==0) return Siz[FindFather(x)]!=1;
int fa=FindFather(x);
if(FindFather(x)!=FindFather(y)) return false;
if(Maxa[fa]!=a|Maxb[fa]!=b) return false;
return true;
}
}Set[Q];
int main(){
read(n),read(m);
for(int i=1;i<=m;i++) read(l[i].s),read(l[i].t),read(l[i].a),read(l[i].b),l2[i]=l[i];
read(K);
for(int i=1;i<=K;i++) read(q[i].s),read(q[i].t),read(q[i].a),read(q[i].b),q[i].no=i;
int Siz=int(sqrt(m*20)),cnt=m/Siz;
for(int i=0;i<=cnt;i++) for(int j=1;j<=n;j++) Set[i].Siz[j]=1;
sort(l+1,l+1+m,cmp1);
sort(l2+1,l2+1+m,cmp1);
for(int i=0;i<=m/Siz;i++) block[i]=l[i*Siz].a;
sort(q+1,q+1+K,cmp2);
sort(l+1,l+1+m,cmp2);
int to=1;
for(int i=1;i<=K;i++){
for(;l[to].b<=q[i].b&&to<=m;to++){
int begin=lower_bound(block,block+1+cnt,l[to].a)-block;
for(int j=begin;j<=cnt;j++) Set[j].Merge(l[to].s,l[to].t,l[to].a,l[to].b,0);
}
int t=upper_bound(block,block+1+cnt,q[i].a)-block-1;
line tmp;tmp.a=block[t];
for(int j=upper_bound(l2+1,l2+1+m,tmp)-l2;j<=m&&l2[j].a<=q[i].a;j++) if(l2[j].b<=q[i].b) Set[t].Merge(l2[j].s,l2[j].t,l2[j].a,l2[j].b,1);
q[i].ans=Set[t].Query(q[i].s,q[i].t,q[i].a,q[i].b);
Set[t].Return();
}
sort(q+1,q+1+K,cmp3);
for(int i=1;i<=K;i++) puts(q[i].ans==1?"Yes":"No");
return 0;
}