洛谷P3247 [HNOI2016]最小公倍数 [分块,并查集]

时间:2022-09-03 15:24:58

洛谷


思路

显然,为了达到这个最小公倍数,只能走\(a,b\)不是很大的边。

即,当前询问的是\(A,B\),那么我们只能走\(a\leq A,b\leq B\)的边。

然而,为了达到这最小公倍数,又需要有\(\max\{a\}=A,\max\{b\}=B\)。

那么暴力做法就很显然了:并查集维护连通块的\(\max\{a\},\max\{b\}\),询问时把满足条件的边全都连上,看最终是否满足条件。

如何优化呢?

把边按\(a\)排序,撒\(\sqrt m\)个关键点,每个关键点把它前面的边按\(b\)排序。

每个询问找出\(a\leq A\)的最靠右关键点,把这个询问放进去。

每个关键点把询问按\(B\)排序。

在关键点回答询问时维护一个从左到右的指针。由于询问的\(B\)递增,指针可以一直从左往右扫。

然后把这个关键点到下一个关键点之间满足条件的边加进去,得到答案,再撤回。

假设\(n,m,q\)同阶(虽然实际上不同阶),则复杂度\(O(n\sqrt n\log n)​\),可能可以调整块大小得到更优的复杂度。


代码

注意\(a,b\)有0,一开始最大值要赋为-1。

由于使用了较多STL,需要开O2

#include<bits/stdc++.h>
clock_t t=clock();
namespace my_std{
using namespace std;
#define pii pair<int,int>
#define fir first
#define sec second
#define MP make_pair
#define rep(i,x,y) for (int i=(x);i<=(y);i++)
#define drep(i,x,y) for (int i=(x);i>=(y);i--)
#define go(x) for (int i=head[x];i;i=edge[i].nxt)
#define sz 101010
#define S 350
typedef long long ll;
typedef double db;
mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
template<typename T>inline T rnd(T l,T r) {return uniform_int_distribution<T>(l,r)(rng);}
inline bool chkmax(int &x,int y){return x<y?x=y,1:0;}
template<typename T>inline void read(T& t)
{
t=0;char f=0,ch=getchar();double d=0.1;
while(ch>'9'||ch<'0') f|=(ch=='-'),ch=getchar();
while(ch<='9'&&ch>='0') t=t*10+ch-48,ch=getchar();
if(ch=='.'){ch=getchar();while(ch<='9'&&ch>='0') t+=d*(ch^48),d*=0.1,ch=getchar();}
t=(f?-t:t);
}
template<typename T,typename... Args>inline void read(T& t,Args&... args){read(t); read(args...);}
void file()
{
#ifndef ONLINE_JUDGE
freopen("a.txt","r",stdin);
#endif
}
inline void chktime()
{
#ifndef ONLINE_JUDGE
cout<<(clock()-t)/1000.0<<'\n';
#endif
}
#ifdef mod
ll ksm(ll x,int y){ll ret=1;for (;y;y>>=1,x=x*x%mod) if (y&1) ret=ret*x%mod;return ret;}
ll inv(ll x){return ksm(x,mod-2);}
#else
ll ksm(ll x,int y){ll ret=1;for (;y;y>>=1,x=x*x) if (y&1) ret=ret*x;return ret;}
#endif
// inline ll mul(ll a,ll b){ll d=(ll)(a*(double)b/mod+0.5);ll ret=a*b-d*mod;if (ret<0) ret+=mod;return ret;}
}
using namespace my_std; int n,m,Q;
struct hh{int u,v,a,b;}edge[sz];
inline bool cmp1(const hh &x,const hh &y){return x.a<y.a;}
inline bool cmp2(const hh &x,const hh &y){return x.b<y.b;}
bool ans[sz]; struct Query{int u,v,a,b,id;};
inline bool cmp(const Query &x,const Query &y){return x.b<y.b;}
int blo,num,val[S],pos[S];
vector<hh>v[S];
vector<Query>q[S];
void init()
{
chktime();
sort(edge+1,edge+m+1,cmp1);
blo=sqrt(m);num=m/blo+(bool)(m%blo);
rep(i,1,num) val[i]=edge[pos[i]=i*blo-blo].a;
chktime();
rep(i,1,num)
{
rep(j,1,pos[i]) v[i].push_back(edge[j]);
sort(v[i].begin(),v[i].end(),cmp2);
}
pos[num+1]=m+1;
}
int fa[sz],mx1[sz],mx2[sz],dep[sz];
int getfa(int x){return x==fa[x]?x:getfa(fa[x]);}
struct hhh{int x,y,mx1,mx2,d;};
hhh merge(hh e)
{
int x=getfa(e.u),y=getfa(e.v);
if (dep[x]>dep[y]) swap(x,y);
hhh ret=(hhh){x,y,mx1[y],mx2[y],dep[y]};
fa[x]=y;
chkmax(mx1[y],max(mx1[x],e.a));chkmax(mx2[y],max(mx2[x],e.b));
if (dep[x]==dep[y]) ++dep[y];
return ret;
}
inline void del(hhh s){fa[s.x]=s.x;mx1[s.y]=s.mx1;mx2[s.y]=s.mx2;dep[s.y]=s.d;}
void solve(int s)
{
sort(q[s].begin(),q[s].end(),cmp);
rep(i,1,n) fa[i]=i,mx1[i]=mx2[i]=-1,dep[i]=1;
int p=0;
rep(i,0,(int)q[s].size()-1)
{
int aa=q[s][i].a,bb=q[s][i].b;
while (p<=(int)v[s].size()-1&&v[s][p].b<=bb) merge(v[s][p]),++p;
stack<hhh>SS;
rep(j,pos[s]+1,pos[s+1]-1)
if (edge[j].a<=aa&&edge[j].b<=bb)
SS.push(merge(edge[j]));
int uu=getfa(q[s][i].u),vv=getfa(q[s][i].v);
if (uu==vv&&mx1[uu]==aa&&mx2[uu]==bb) ans[q[s][i].id]=1;
while (!SS.empty()) del(SS.top()),SS.pop();
}
} int main()
{
file();
int x,y,a,b;
read(n,m);
rep(i,1,m) read(x,y,a,b),edge[i]=(hh){x,y,a,b};
chktime();
read(Q);
init();
chktime();
rep(i,1,Q)
{
read(x,y,a,b);
int t=upper_bound(val+1,val+num+1,a)-val-1;
q[t].push_back((Query){x,y,a,b,i});
}
chktime();
rep(i,1,num) solve(i);
rep(i,1,Q) puts(ans[i]?"Yes":"No");
chktime();
return 0;
}