[HNOI2016]最小公倍数

Description:

给你一个图,每条边有\(a_i,b_i\)两个属性,每次询问两个点间有没有一条路径满足路径上的点\(max_{a_i}=x_i,max_{b_i}=y_i\)

Hint:

\(n,q\le 5*10^4,q\le 10^5\)

Solution:

有点毒瘤....

这种题一般要考虑把询问离线排序,消除其中一个量的影响,然后枚举另一个量

所以我们把询问按\(b_i\)排序,按\(b_i\)从小到大把边依次加入图中,用并查集维护

但这样对\(a_i\)的枚举还是\(O(n)\)的,所以总复杂度\(O(n^2)\)

考虑把复杂度均摊

先把\(a_i\)按大小分块,我们加\(b_i\)时,给所有块内最小值大于对应\(a_i\)的块更新

每次查询再去按\(a_i\)从小到大枚举一部分"漏更新"的边角料\(a_i\)

这样每次修改\(\sqrt{n}\),枚举也是最多\(\sqrt{n}\),这样复杂度就降下来了

因为每次要撤销边角料的修改,所以用按秩合并的并查集+栈来搞

#include <map>
#include <set>
#include <stack>
#include <cmath>
#include <queue>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#define ls p<<1 
#define rs p<<1|1
using namespace std;
typedef long long ll;
const int mxn=1e5+500;
int n,m,ans[mxn],hd[mxn];

inline int read() {
    char c=getchar(); int x=0,f=1;
    while(c>'9'||c<'0') {if(c=='-') f=-1;c=getchar();}
    while(c<='9'&&c>='0') {x=(x<<3)+(x<<1)+(c&15);c=getchar();}
    return x*f;
}
inline void chkmax(int &x,int y) {if(x<y) x=y;}
inline void chkmin(int &x,int y) {if(x>y) x=y;}

int size,k,blk[mxn];

struct Line {
    int u,v,a,b,id;
    friend bool operator < (Line x,Line y) {
        return x.a<y.a;
    }
}t[mxn],T[mxn],q[mxn];

int cmp1(Line x,Line y) {
    return x.a<y.a;
}

int cmp2(Line x,Line y) {
    return x.b<y.b;
}

struct ifm {
    int type,x,y,z;
}stk[mxn];
int top;

struct Un {
    int fa[mxn],sz[mxn],mxa[mxn],mxb[mxn];
    int find(int x) {
        return fa[x]==0?x:find(fa[x]);
    }
    void merge(int x,int y,int a,int b,int opt) {
        int u=find(x),v=find(y);
        if(sz[u]>sz[v]) 
            swap(x,y),swap(u,v);
        if(opt==1) 
            stk[++top]=(ifm){1,v,mxa[v],mxb[v]}; 
        chkmax(mxa[v],max(mxa[u],a));
        chkmax(mxb[v],max(mxb[u],b));
        if(u==v) return ;
        fa[u]=v; sz[v]+=sz[u]; //按秩合并,这样才能回溯,且不能路径压缩
        if(opt==1) 
            stk[++top]=(ifm){0,u,v,sz[u]}; 
    }
    void backdate() {
        while(top) {
            if(stk[top].type==0) 
                fa[stk[top].x]=0,sz[stk[top].y]-=stk[top].z;
            else 
                mxa[stk[top].x]=stk[top].y,
                mxb[stk[top].x]=stk[top].z;
            --top;
        }
    }
    int query(int x,int y,int a,int b) {
        int anc=find(x);
        if(x==y&&a==0&&b==0) return sz[anc]!=1; //判自环
        if(anc!=find(y)) return 0;
        if(mxa[anc]!=a||mxb[anc]!=b) return 0;
        return 1;
    }
}st[1005];

int main()
{
    n=read(); m=read();
    for(int i=1;i<=m;++i) 
        t[i].u=read(),t[i].v=read(),t[i].a=read(),t[i].b=read(),T[i]=t[i];
    k=read();
    for(int i=1;i<=k;++i) 
        q[i].u=read(),q[i].v=read(),q[i].a=read(),q[i].b=read(),q[i].id=i;
    int size=int(sqrt(m*20)),cnt=m/size;
    for(int i=0;i<=cnt;++i) 
        for(int j=1;j<=n;++j) 
            st[i].sz[j]=1;
    sort(t+1,t+m+1,cmp1); sort(T+1,T+m+1,cmp1);
    for(int i=0;i<=cnt;++i) blk[i]=t[i*size].a;
    sort(q+1,q+k+1,cmp2); sort(t+1,t+m+1,cmp2); int pos=1;
    for(int i=1;i<=k;++i) {
        while(t[pos].b<=q[i].b&&pos<=m) {
            int bg=lower_bound(blk,blk+cnt+1,t[pos].a)-blk; 
            //找到这条边该插的最小的块
            for(int j=bg;j<=cnt;++j) 
                st[j].merge(t[pos].u,t[pos].v,t[pos].a,t[pos].b,0); 
            ++pos;
        }
        int x=upper_bound(blk,blk+cnt+1,q[i].a)-blk-1; //找到询问的a对应的块
        Line tp; tp.a=blk[x]; int tpos=upper_bound(T+1,T+m+1,tp)-T;
        while(T[tpos].a<=q[i].a&&tpos<=m) { //更新零碎的a关键字边
            if(T[tpos].b<=q[i].b) 
                st[x].merge(T[tpos].u,T[tpos].v,T[tpos].a,T[tpos].b,1);
            ++tpos; 
        }
        ans[q[i].id]=st[x].query(q[i].u,q[i].v,q[i].a,q[i].b);
        st[x].backdate(); //撤回插入的a关键字边
    }
    for(int i=1;i<=k;++i) ans[i]==1?puts("Yes"):puts("No");
    return 0;
}

posted @ 2019-03-27 20:17  cloud_9  阅读(170)  评论(0编辑  收藏  举报