4537: [Hnoi2016]最小公倍数|分块

暴力的做法就是直接找到所有 a,b 都小于等于某个询问的边然后并查集合并,维护每个集合的 a,b 得最大值看是否等于询问的 a,b
然后就可以考虑分块,把边按照 a 排序,每隔n分为一块
块前的按照 b 值排序按顺序插入,块内的暴力判断,并查集合并,每次都把块内合并的记录下来,处理完某个询问时就撤回并查集的操作
块的大小为n可能会 T 改成nlog2n可能会快一点

#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<ctime>
#include<set>
#include<map>
#define N 110000
#define ll long long
using namespace std;
int sc()
{
    int i=0,f=1; char c=getchar();
    while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9')i=i*10+c-'0',c=getchar();
    return i*f;
}
struct W{int x,y,a,b,p;}a[N],q[N];
int bl[N],c[N],fa[N],size[N],ma[N],mb[N];
int st[N],SIZE[N],MA[N],MB[N],ans[N];
int n,m,Q,top,cnt,block;
bool cmp1(W a,W b)
{
    return a.a<b.a||(a.a==b.a&&a.b<b.b);
}
bool cmp2(W a,W b)
{
    return a.b<b.b||(a.b==b.b&&a.a<b.a);
}
int find(int x)
{
    return x==fa[x]?x:find(fa[x]);
}
void merge(int x,int y,int a,int b,int v)
{
    int fx=find(x);
    int fy=find(y);
    if(v==1)
    {
        ++cnt;
        st[cnt]=fx;
        MA[cnt]=ma[fx];
        MB[cnt]=mb[fx];
        SIZE[cnt]=size[fx];
        ++cnt;
        st[cnt]=fy;
        MA[cnt]=ma[fy];
        MB[cnt]=mb[fy];
        SIZE[cnt]=size[fy];
    }
    if(fx==fy)
    {
        ma[fx]=max(ma[fx],a);
        mb[fx]=max(mb[fx],b);
    }
    else
    {
        if(size[fx]<size[fy])swap(fx,fy);
        fa[fy]=fx;
        size[fx]+=size[fy];
        ma[fx]=max(ma[fx],max(a,ma[fy]));
        mb[fx]=max(mb[fx],max(b,mb[fy]));
    }
}
void retract()
{
    for(int i=cnt;i>=1;i--)
    {
        int k=st[i];
        fa[k]=k;
        ma[k]=MA[i];
        mb[k]=MB[i];
        size[k]=SIZE[i];
    }
}

int main()
{
    n=sc();m=sc();block=sqrt(m*log2(m));
    for(int i=1;i<=m;i++)
    {
        a[i].x=sc();
        a[i].y=sc();
        a[i].a=sc();
        a[i].b=sc();
    }
    Q=sc();
    for(int i=1;i<=Q;i++)
    {
        q[i].x=sc();
        q[i].y=sc();
        q[i].a=sc();
        q[i].b=sc();
        q[i].p=i;
    }
    sort(a+1,a+m+1,cmp1);
    sort(q+1,q+Q+1,cmp2);
    for(int i=1;i<=m;i++) 
        bl[i]=(i-1)/block+1;
    for(int i=1;i<=m;i+=block)
    {
        int top=0,now=1;
        for(int j=1;j<=Q;j++)
            if(q[j].a>=a[i].a&&(i+block>m||q[j].a<a[i+block].a))
                c[++top]=j;
        sort(a+1,a+i,cmp2);
        for(int j=1;j<=n;j++)
        {
            fa[j]=j;
            ma[j]=-1;
            mb[j]=-1;
            size[j]=1;
        }
        for(int j=1;j<=top;j++)
        {
            int k=c[j];
            while(now<i&&a[now].b<=q[k].b)
                 merge(a[now].x,a[now].y,a[now].a,a[now].b,0),now++;
            for(int l=i;bl[l]==bl[i];l++)
                if(a[l].a<=q[k].a&&a[l].b<=q[k].b)
                     merge(a[l].x,a[l].y,a[l].a,a[l].b,1);
            int fx=find(q[k].x),fy=find(q[k].y);
            ans[q[k].p]=(fx==fy&&ma[fx]==q[k].a&&mb[fx]==q[k].b);
            retract(); cnt=0;
        }
    }
    for(int i=1;i<=Q;i++)
        puts(ans[i]?"Yes":"No");
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值