题目
前言
来来回回调了好久… 各种 (mathtt{qqgg}) 的锅层出不穷。最后 (mathtt{T}) 飞了以为是自己常数大,结果是之前调的时候为了方便改了一个循环初始变量 —— 单点询问 (mathcal O(mcdot log n)) 复杂度 ( ext{get})!
解法
首先有一个暴力:对于每个询问 (i),枚举所有 (ale a_i,ble b_i) 的边加入带权并查集,然后查询 ((u_i,v_i)) 是否连通且连通块的 (max a=a_i,max b=b_i) —— 单点询问 (mathcal O(mcdot alpha (n))) 复杂度 ( ext{get})!(悲
将边从小到大以 (a) 排序并分块,将询问从小到大以 (b) 排序。对于每个询问 (i),找到一个块 (j),使得在 ([1,j]) 中的块中的边都满足 (ale a_i),并把 (i) 加入集合 (be_j) 中。枚举 (i=[0,cnt])(其中 (cnt) 是块的总数),将属于前 (i) 个块的边从小到大以 (b) 排序。再枚举 (jin be_i)(按 (b) 从小到大的顺序),这样属于前 (i) 个块的边可以用 ( ext{two-pointers}) 的方式加入,复杂度 (mathcal Oleft ((B+m)cdot frac{m}{B}cdot alpha (n) ight)=mathcal Oleft (left(m+ frac{m^2}{B} ight)cdot alpha (n) ight))(等差数列求和)。对于第 (i+1) 块的边就只有暴力判断,但此时的并查集需要带撤销,可能是 (mathcal O(qBcdot log n)) 的?
至于 "将属于前 (i) 个块的边从小到大以 (b) 排序" 操作可以使用归并排序来减少复杂度。
解了一下 (B) 的最优取值:(sqrt{frac{m^2}{qcdot log n}})。不知道有啥用。
其实也可以不使用带撤销并查集。加入前 (i) 个块的边后将其缩点,然后对于第 (i+1) 个块的边进行 (mathtt{bfs}) 即可。
总结
当需要维护偏序时,可以先分块解决一个偏序。再在某块中暴力计算答案。
代码
#include <cstdio>
#define print(x,y) write(x),putchar(y)
template <class T>
inline T read(const T sample) {
T x=0; char s; bool f=0;
while((s=getchar())>'9' or s<'0')
f|=(s=='-');
while(s>='0' and s<='9')
x=(x<<1)+(x<<3)+(s^48),
s=getchar();
return f?-x:x;
}
template <class T>
inline void write(const T x) {
if(x<0) {
putchar('-'),write(-x);
return;
}
if(x>9) write(x/10);
putchar(x%10^48);
}
#include <cmath>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn=5e4+5,maxm=1e5+5;
const int maxb=320;
vector <int> be[maxb];
int n,m,B,R[maxb],q,ans[maxn];
struct Edge {
int u,v,a,b;
bool operator < (const Edge &y) const {
return a<y.a;
}
} e[maxm];
struct Query {
int u,v,a,b,id;
bool operator < (const Query &y) const {
return b<y.b;
}
} s[maxn];
struct DSU {
int tp,f[maxn],rk[maxn];
int ma[maxn],mb[maxn];
struct store {
int U,V,A,B,Rk;
} sta[maxm];
void init() {
for(int i=1;i<=n;++i)
f[i]=i,rk[i]=1,
ma[i]=mb[i]=-1;
}
int Find(int x) {
return x==f[x]?x:Find(f[x]);
}
void Merge(int u,int v,int aa,int bb) {
u=Find(u),v=Find(v);
if(rk[u]<rk[v]) swap(u,v);
sta[++tp]=(store){u,v,ma[u],mb[u],rk[u]};
if(u^v) {
f[v]=u,rk[u]+=rk[v];
}
ma[u]=max(ma[u],max(ma[v],aa));
mb[u]=max(mb[u],max(mb[v],bb));
}
int FindSet(int x) {
return x==f[x]?x:f[x]=FindSet(f[x]);
}
void Unite(int u,int v,int aa,int bb) {
u=FindSet(u),v=FindSet(v);
if(rk[u]<rk[v]) swap(u,v);
if(u^v) {
f[v]=u,rk[u]+=rk[v];
}
ma[u]=max(ma[u],max(ma[v],aa));
mb[u]=max(mb[u],max(mb[v],bb));
}
void rec() {
store t;
while(tp) {
t=sta[tp--];
f[t.V]=t.V,rk[t.U]=t.Rk;
ma[t.U]=t.A,mb[t.U]=t.B;
}
}
} dsu;
bool cmp(const Edge &x,const Edge &y) {
return x.b<y.b;
}
void Sort(int bl) {
static Edge t[maxm];
static int i,j,cnt,r;
i=cnt=0; r=j=(bl-1)*B;
while(i<r and j<R[bl]) {
if(e[i+1].b<e[j+1].b)
t[++cnt]=e[++i];
else t[++cnt]=e[++j];
}
while(i<r) t[++cnt]=e[++i];
while(j<R[bl]) t[++cnt]=e[++j];
for(int i=1;i<=R[bl];++i)
e[i]=t[i];
}
int main() {
n=read(9),m=read(9);
B=sqrt(1.0*m);
int lim=(m-1)/B+1;
for(int i=1;i<=m;++i)
e[i].u=read(9),e[i].v=read(9),
e[i].a=read(9),e[i].b=read(9);
sort(e+1,e+m+1);
for(int i=1;i<=lim;++i)
R[i]=min(m,i*B);
q=read(9);
for(int i=1;i<=q;++i) {
s[i].u=read(9),s[i].v=read(9),
s[i].a=read(9),s[i].b=read(9);
s[i].id=i;
}
sort(s+1,s+q+1);
for(int i=1;i<=q;++i) {
int j=0;
while(j<lim and e[R[j+1]].a<=s[i].a)
++j;
be[j].push_back(i);
}
for(int i=1;i<=lim;++i)
sort(e+(i-1)*B+1,e+R[i]+1,cmp);
for(int i=0;i<=lim;++i) {
dsu.init();
int pos=0;
for(int J=0;J<be[i].size();++J) {
int j=be[i][J];
while(pos<R[i] and e[pos+1].b<=s[j].b)
++pos,dsu.Unite(e[pos].u,e[pos].v,e[pos].a,e[pos].b);
for(int k=i*B+1;k<=R[i+1];++k) {
if(s[j].b<e[k].b) break;
if(s[j].a>=e[k].a)
dsu.Merge(e[k].u,e[k].v,e[k].a,e[k].b);
}
s[j].u=dsu.Find(s[j].u),s[j].v=dsu.Find(s[j].v);
ans[s[j].id]=(s[j].u==s[j].v and dsu.ma[s[j].u]==s[j].a and dsu.mb[s[j].u]==s[j].b);
dsu.rec();
}
if(i^lim) Sort(i+1);
}
for(int i=1;i<=q;++i)
puts(ans[i]?"Yes":"No");
return 0;
}