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

正解:分块+并查集

解题报告:

传送门!

真的好神仙昂QAQ,,,完全想不出来,,,还是太菜了QAQ

首先还是要说下,这题可以用K-D Tree乱搞过去(数据结构是个好东西昂,,,要多学学QAQ),但是我不会,暂时也不打算学更不打算写这种方法,所以只是提一下可以用这个姿势过去QAQ

然后说下另外一个方法,神仙一般的(分块+并查集),,,

首先要get一个套路,是这样儿的:

对于这种有两种限制的题目

一般的套路就是条件按照第一种权值为关键字排序,询问按照第二种关键字排序

然后对于条件先按第一关键字分块,然后在块内部按第二关键字排序

这样当前块前面的点都是符合当前询问点对于第一关建字条件的

而且第二关键字都是单调的,所以就可以two−pointers扫一下

然后对于每个询问,暴力处理一下当前块的贡献

然后回归这道题,首先可以想到这道题的话,为了方便区分给定条件和询问,在这里define一下,条件是ab询问是AB

显然最小公倍数的话就是说amax=A,bmax=B嘛

首先考虑到显然可以先把a>A,b>B的都排除了

然后就可以考虑可不可以把剩下的合法的连起来,然后用个并查集维护一下两个点是否连起来了,然后再判断一下联通块内的amax&bmax是否等于A,B

然后具体做法就是

对条件按a排序并分个块,然后把块内部的边按b排序,对询问按B排序

然后对每个块分别做,设当前块的还麻油按照b排序的时候的左端点的a为li右端点的a为ri

然后先找出所有a在[li,ri]的询问

考虑到对第i个块的第j个询问,有贡献的条件有哪些

1)前面i-1个块中的b<Bj的边

因为考虑到最初块是按a排序的,即使是在内部排序之后也是能保证前面i-1块中的任意一条边的a<当前块中的a

所以只要是b满足条件就好

这里又考虑到已经对于询问按照B排序了,那如果前面i-1块的b也是按顺序的,就可以直接two-pointers做了,所以就再对前i-1块整体按b排个序

2)当前第i块中满足b<Bj,a<Aj的边

分块的中心思想不是就,大段维护,局部朴素嘛

这里考虑到反正每个块都只有√m条边,所以直接暴力地做就好

但是还要想一个问题

显然是可能有对之前的询问满足条件但是对当前询问不满足条件了的嘛

所以如果对他们做并查集的时候就要考虑怎么撤销

如果全部重新建一个并查集肯定也是布星的,因为前面那个第一种情况是不用撤销的昂

所以考虑就不用路径压缩了,但还是要优化一下不然过不去,所以用个按秩合并

然后就做完辣!484很妙QAQ!

 

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define rg register
#define gc getchar()
#define rp(i,x,y) for(rg int i=x;i<=y;++i)
#define my(i,x,y) for(rg int i=x;i>=y;--i)

const int N=50000+10,M=100000+10,sqN=300+10;
int n,m,q,fa[N],blk,sz[N],amx[N],bmx[N],cnt,siz,r[sqN],as[N],ed_tmp,sz_tmp[N];
struct ed{int fr,to,a,b,bl;}edge[M],ques[N],tmp[N],tmptmp[N];

il int read()
{
    char ch=gc;int x=0;bool y=1;
    while(ch!='-' && (ch>'9' || ch<'0'))ch=gc;
    if(ch=='-')ch=gc,y=0;
    while(ch>='0' && ch<='9')x=(x<<1)+(x<<3)+(ch^'0'),ch=gc;
    return y?x:-x;
}
il bool cmpa(ed gd,ed gs){return gd.a==gs.a?gd.b<gs.b:gd.a<gs.a;}
il bool cmpb(ed gd,ed gs){return gd.b==gs.b?gd.a<gs.a:gd.b<gs.b;}
il void clr(){rp(i,1,n)fa[i]=i,sz[i]=1,amx[i]=bmx[i]=-1;cnt=0;}
il int fd(int x){return fa[x]==x?x:fd(fa[x]);}
il void mg(int x,int y,int a,int b)
{
    int fax=fd(x),fay=fd(y);if(sz[fax]>sz[fay])swap(fax,fay);
    tmptmp[++ed_tmp]=(ed){fax,fay,amx[fay],bmx[fay],sz[fay]};
    if(fax^fay)fa[fax]=fay,sz[fay]+=sz[fax],amx[fay]=max(amx[fay],amx[fax]),bmx[fay]=max(bmx[fay],bmx[fax]);
    amx[fay]=max(amx[fay],a);bmx[fay]=max(bmx[fay],b);
}

int main()
{
    n=read();m=read();blk=sqrt(m*log2(n));rp(i,1,m)edge[i]=(ed){read(),read(),read(),read(),0};q=read();rp(i,1,q)ques[i]=(ed){read(),read(),read(),read(),i};
    sort(edge+1,edge+1+m,cmpa);sort(ques+1,ques+1+q,cmpb);rp(i,1,m)edge[i].bl=(i-1)/blk+1,r[(i-1)/blk+1]=i;siz=edge[m].bl;
    rp(i,1,siz)
    {
        clr();
        rp(j,1,q)if(edge[r[i-1]+1].a<=ques[j].a && (edge[r[i-1]+1+blk].a>ques[j].a || r[i-1]+1+blk>m))tmp[++cnt]=ques[j];if(!cnt)continue;
        if(i^1)sort(edge+1,edge+r[i-1]+1,cmpb);int rpoint=1;
        rp(j,1,cnt)
        {
            while(rpoint<r[i-1]+1 && edge[rpoint].b<=tmp[j].b)mg(edge[rpoint].fr,edge[rpoint].to,edge[rpoint].a,edge[rpoint].b),++rpoint;
            ed_tmp=0;
            rp(k,r[i-1]+1,min(r[i-1]+blk,m))if(edge[k].a<=tmp[j].a && edge[k].b<=tmp[j].b)mg(edge[k].fr,edge[k].to,edge[k].a,edge[k].b);
            int fr=fd(tmp[j].fr),to=fd(tmp[j].to);
            as[tmp[j].bl]=(fr==to && amx[fr]==tmp[j].a && bmx[fr]==tmp[j].b);
            my(k,ed_tmp,1)fr=tmptmp[k].fr,to=tmptmp[k].to,fa[fr]=fr,amx[to]=tmptmp[k].a,bmx[to]=tmptmp[k].b,sz[to]=tmptmp[k].bl;
            ed_tmp=1;
        }
    }
    rp(i,1,q)as[i]?printf("Yes\n"):printf("No\n");
    return 0;
}
然后这儿是代码QAQ!

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值