P3247 [HNOI2016]最小公倍数

P3247 [HNOI2016]最小公倍数

先考虑如果只有一个限制该怎么做。

一个简单的思路就是离线下来,然后排序过后双指针扫描加边和处理询问即可,用并查集维护。

或者每次询问的时候都暴力遍历所有边然后并查集。

那么现在有了两个限制,单独并不好做

考虑优化这个过程,我们可以先按 \(a\) 排序然后把询问分块,然后相当于对于一个块之前的所有边的 \(a\) 的相对大小都是固定下来的,那么对于这些边我们可以直接考虑对当前的块中询问做双指针,然后对于当前块的边,我们在每次询问的时候都枚举一遍所有边然后加入即可。

每次操作完了之后要记得把当前的块中的边按 \(a\) 排序过后归并进边集合内。

这需要可撤销并查集来维护,时间复杂度是 \(O(n\sqrt{n}logn)\)

还可以继续优化。

发现对于块内的边,我们可以直接BFS一遍,然后找到路径最大值就可以回答询问了,这里可以使用路径压缩按秩合并并查集,时间复杂度 \(O(n\sqrt{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;
}

posted @ 2021-04-16 10:27  __Anchor  阅读(73)  评论(0编辑  收藏  举报