题目
前言
来来回回调了好久… 各种 \(\mathtt{qqgg}\) 的锅层出不穷。最后 \(\mathtt{T}\) 飞了以为是自己常数大,结果是之前调的时候为了方便改了一个循环初始变量 —— 单点询问 \(\mathcal O(m\cdot \log n)\) 复杂度 \(\text{get}\)!
解法
首先有一个暴力:对于每个询问 \(i\),枚举所有 \(a\le a_i,b\le b_i\) 的边加入带权并查集,然后查询 \((u_i,v_i)\) 是否连通且连通块的 \(\max a=a_i,\max b=b_i\) —— 单点询问 \(\mathcal O(m\cdot \alpha (n))\) 复杂度 \(\text{get}\)!(悲
将边从小到大以 \(a\) 排序并分块,将询问从小到大以 \(b\) 排序。对于每个询问 \(i\),找到一个块 \(j\),使得在 \([1,j]\) 中的块中的边都满足 \(a\le a_i\),并把 \(i\) 加入集合 \(be_j\) 中。枚举 \(i=[0,cnt]\)(其中 \(cnt\) 是块的总数),将属于前 \(i\) 个块的边从小到大以 \(b\) 排序。再枚举 \(j\in be_i\)(按 \(b\) 从小到大的顺序),这样属于前 \(i\) 个块的边可以用 \(\text{two-pointers}\) 的方式加入,复杂度 \(\mathcal O\left ((B+m)\cdot \frac{m}{B}\cdot \alpha (n)\right)=\mathcal O\left (\left(m+ \frac{m^2}{B}\right)\cdot \alpha (n)\right)\)(等差数列求和)。对于第 \(i+1\) 块的边就只有暴力判断,但此时的并查集需要带撤销,可能是 \(\mathcal O(qB\cdot \log n)\) 的?
至于 "将属于前 \(i\) 个块的边从小到大以 \(b\) 排序" 操作可以使用归并排序来减少复杂度。
解了一下 \(B\) 的最优取值:\(\sqrt{\frac{m^2}{q\cdot \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;
}