[BZOJ4537] [HNOI/AHOI2016] 最小公倍数 - 分块 - 并查集

本文介绍了一道HNOI2016竞赛题目“最小公倍数”的高效解法,通过边的分块、排序及并查集等算法优化,实现了对大量询问的有效处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

考场爆零很是不爽……

好吧讲道理这题其实很裸

4537: [Hnoi2016]最小公倍数

Time Limit: 40 Sec   Memory Limit: 512 MB
Submit: 575   Solved: 243
[ Submit][ Status][ Discuss]

Description

  给定一张N个顶点M条边的无向图(顶点编号为1,2,…,n),每条边上带有权值。所有权值都可以分解成2^a*3^b
的形式。现在有q个询问,每次询问给定四个参数u、v、a和b,请你求出是否存在一条顶点u到v之间的路径,使得
路径依次经过的边上的权值的最小公倍数为2^a*3^b。注意:路径可以不是简单路径。下面是一些可能有用的定义
:最小公倍数:K个数a1,a2,…,ak的最小公倍数是能被每个ai整除的最小正整数。路径:路径P:P1,P2,…,Pk是顶
点序列,满足对于任意1<=i<k,节点Pi和Pi+1之间都有边相连。简单路径:如果路径P:P1,P2,…,Pk中,对于任意1
<=s≠t<=k都有Ps≠Pt,那么称路径为简单路径。

Input

  输入文件的第一行包含两个整数N和M,分别代表图的顶点数和边数。接下来M行,每行包含四个整数u、v、a、
b代表一条顶点u和v之间、权值为2^a*3^b的边。接下来一行包含一个整数q,代表询问数。接下来q行,每行包含四
个整数u、v、a和b,代表一次询问。询问内容请参见问题描述。1<=n,q<=50000、1<=m<=100000、0<=a,b<=10^9

Output

  对于每次询问,如果存在满足条件的路径,则输出一行Yes,否则输出一行 No(注意:第一个字母大写,其余
字母小写) 。

Sample Input

4 5
1 2 1 3
1 3 1 2
1 4 2 1
2 4 3 2
3 4 2 2
5
1 4 3 3
4 2 2 3
1 3 2 2
2 3 2 2
1 3 4 4

Sample Output

Yes
Yes
Yes
No
No

HINT

Source

[ Submit][ Status][ Discuss]

HOME   Back

首先我们会想到一个暴力做法,那就是对于每个询问,插入所有满足a不大于询问的a,且b不大于询问的b的边并判断是否u和v是否在一个a的最大值即询问的a,b的最大值即询问的b的连通块内。显然这样做是O(mq)的,显然要TLE。

那么想到这就不难想到分块了。首先我们对边直接分块,先关于权值a排序,每块的大小是m^0.5,共有m^0.5个块。

然后我们可以将询问分配到每个块中。对于每个块中的所有询问,我们考虑对这个块以前的所有边暴力按b排序,因为这些边权显然都满足a比询问的a小,对于一个块中的询问可以在O(m log m)的时间内解决这部分的边,可以将它们插入并查集。

然后还剩下一些零零散散的边,即在询问所在的权值块内,由于边只有m^0.5条,即使我们对于所有的询问暴力枚举查找可以加的边也是O(m^0.5)每个询问的,这时我们就要考虑如何使并查集在加了边之后又能回到之前的状态。

其实很简单,我们知道并查集如果按秩合并的话最坏的高度也就只有logn层(完全二叉树),因此我们可以考虑不进行路径压缩,只是按秩合并,将修改前的记录保存入一个栈内,每次询问结束时复原即可。

时间复杂度 O((m log m+q log n)m^0.5)

/**************************************************************
    Problem: 4537
    User: whzzt
    Language: C++
    Result: Accepted
    Time:11088 ms
    Memory:19648 kb
****************************************************************/
 
#include "stdio.h"
#include "algorithm"
#include "iostream"
#include "string.h"
#include "stdlib.h"
#include "math.h"
#include "vector"
#include "map"
#include "set"
 
using namespace std;
 
const int L=10000005;
char _buff[L]; int _pos=-1;
void ReadIn(){fread(_buff,L,sizeof(char),stdin);}
#define fge _buff[++_pos]
inline int read(){
    int x=0; char ch=fge;
    while(ch>'9'||ch<'0')ch=fge;
    while(ch<='9'&&ch>='0')
        x=x*10+ch-'0',ch=fge;
    return x;
}
const int N=50005,M=100005;
struct E{int u,v,a,b;} e[M],q[M],st[M],es[M];
inline bool cmpa(const E&a,const E&b)
{ return a.a==b.a?a.b<b.b:a.a<b.a;}
inline bool cmpb(const E&a,const E&b)
{ return a.b==b.b?a.a<b.a:a.b<b.b;}
inline bool cmpv(const int&u,const int&v)
{ E a=q[u],b=q[v];return a.b==b.b?a.a<b.a:a.b<b.b;}
inline void init(E&a){a.u=read(),a.v=read(),a.a=read(),a.b=read();}
int n,m,Q,lim,top,ans[N],l,r,la,in[N];
int fa[N],siz[N],maxa[N],maxb[N],val[N];
int tmp,rq[N],sz[N],ma[N],mb[N],p[N];
 
int find(int x){return fa[x]==x?x:find(fa[x]);}
 
void merge(E a,int type){
    int fu=find(a.u),fv=find(a.v);
    if(siz[fu]>siz[fv])swap(fu,fv);
    if(type){ 
        rq[++tmp]=fv;sz[tmp]=siz[fv];p[tmp]=fu;
        ma[tmp]=maxa[fv];mb[tmp]=maxb[fv];
    }
    if(fu!=fv){
        fa[fu]=fv;siz[fv]+=siz[fu];
        maxa[fv]=max(maxa[fu],maxa[fv]);
        maxb[fv]=max(maxb[fu],maxb[fv]);
    }
    maxa[fv]=max(maxa[fv],a.a);
    maxb[fv]=max(maxb[fv],a.b);
}
 
int main(){
    ReadIn();n=read(),m=read();int i,j,k;
    for(i=1;i<=m;i++)init(e[i]);Q=read();
    for(i=1;i<=Q;i++)init(q[i]);lim=(int)sqrt(m+.1);
    sort(e+1,e+m+1,cmpa);
    for(i=0;i<=m;i+=lim){
        l=i+1,r=min(i+lim,m);
        for(j=1,top=0;j<=Q;j++)
            if(q[j].a>=e[l].a&&(q[j].a<e[r].a||(r==m||e[r].a<e[r+1].a)&&e[r].a==q[j].a))
                val[++top]=j;
        if(top==0)continue;
        sort(val+1,val+top+1,cmpv);
        for(j=1;j<=top;j++)st[j]=q[val[j]];
        for(j=1;j<=n;j++)fa[j]=j,maxa[j]=maxb[j]=-1,siz[j]=1;
        memcpy(es+1,e+1,sizeof(E)*i);sort(es+1,es+i+1,cmpb);
        for(k=la=1;k<=top;k++){
            for(;la<=i;la++)
                if(es[la].b<=st[k].b)
                    merge(es[la],0);
                else break;
            for(j=l;j<=r;j++){
                if(e[j].a<=st[k].a&&e[j].b<=st[k].b)
                    merge(e[j],1);
            } j=find(st[k].u);
            if(maxa[j]==st[k].a&&maxb[j]==st[k].b&&j==find(st[k].v))ans[val[k]]=1;
            for(;tmp;tmp--){
                fa[p[tmp]]=p[tmp];siz[rq[tmp]]=sz[tmp];
                maxa[rq[tmp]]=ma[tmp];maxb[rq[tmp]]=mb[tmp];
                p[tmp]=rq[tmp]=ma[tmp]=mb[tmp]=sz[tmp]=0;
            }
        }
    }
    for(i=1;i<=Q;i++)puts(ans[i]?"Yes":"No");
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值