luoguP3247 [HNOI2016]最小公倍数 题解

题意

给定一张 N 个顶点 M 条边的无向图(顶点编号为 1,2,,n),每条边上带有权值。所有权值都可以分解成 2a×3b 的形式。

现在有 q 个询问,每次询问给定四个参数 u,v,ab,请你求出是否存在一条顶点 uv 之间的路径,使得路径依次经过的边上的权值的最小公倍数为 2a×3b

注意:路径可以不是简单路径。

1n,q5×1041m1050a,b109

思路

一步一步来。

暴力

先分析给定的条件:满足经过边边权的 lcm2a×3b。那我们可以拆解成:每条边有 x 属性和 y 属性,所经过边的 x 属性最大值为 a,且 a 必须出现过。y 属性同理。

那么暴力想法随即而出:预处理出每个联通块内的边,记录到桶中。对于每一组询问,将不符合的边删掉,在新图上跑 BFS,记录目标两点能否联通,且经过边是否符合条件,判断并输出。复杂度 O(qm)

正解

运用到经典思想:离线,边加边询问。则考虑分块。对于每条边先按照第一维排序,将其分为若干个块,处理出块尾元素的 x 值,记为 B 数组。对于每一组询问,按照第二维排序。在 B 数组中查找对应的 x 值应当所在的块,将询问加入该块的待处理序列中。此时,对于每一组块中的询问,已经保证了 x 元素一定合法,只需要暴力判断 y 元素的合法性即可。由于加边是一次性的,处理下次询问时需要撤回影响,所以需要使用到可撤销并查集。复杂度 O(mqα(n))

Code

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define db double
#define mkp make_pair
#define pb push_back
#define P pair<int,int>
#define _ 0
const int N=1e5+10,mod=1e9+7,MOD=1e9+123,inf=1e18;
int T=1,n,m,Q,L[N],R[N],b[N],ans[N],fa[N],son[N],mxa[N],mxb[N],top;
vector<int> v[N];
struct node{
    int u,v,son,a,b,id;
}e[N],q[N],sta[N];
bool cmp1(node x,node y){
    return x.a<y.a;
}
bool cmp2(node x,node y){
    return x.b<y.b;
}
void mem(){
    for(int i=1;i<=n;i++){
        fa[i]=i;
        son[i]=0;
        mxa[i]=mxb[i]=-1;
    }
}
int find(int x){
    return x==fa[x]?x:find(fa[x]);
}
void add(int u,int v,int a,int b){
    int fau=find(u),fav=find(v);
    if(son[fau]<son[fav]) swap(fau,fav);
    sta[++top]=(node){fau,fav,son[fau],mxa[fau],mxb[fau],0};
    mxa[fau]=max(mxa[fau],a),mxb[fau]=max(mxb[fau],b);
    if(fau==fav) return ;
    fa[fav]=fau,son[fau]=max(son[fau],son[fav]+1);
    mxa[fau]=max(mxa[fau],mxa[fav]),mxb[fau]=max(mxb[fau],mxb[fav]);
}
void del(int id){
    fa[sta[id].v]=sta[id].v;
    mxa[sta[id].u]=sta[id].a;
    mxb[sta[id].u]=sta[id].b;
    son[sta[id].u]=sta[id].son;
}
void solve(){
    cin>>n>>m;
    int p=sqrt(1.0*m*log(m));
    int siz=m/p;
    for(int i=1;i<=m;i++){
        cin>>e[i].u>>e[i].v>>e[i].a>>e[i].b;
    }
    sort(e+1,e+1+m,cmp1);
    for(int i=1;i<=siz;i++){
        L[i]=(i-1)*p+1,R[i]=i*p;
        b[i]=e[L[i]].a;
    }
    sort(b+1,b+1+siz);
    R[siz]=max(m,R[siz]);
    cin>>Q;
    for(int i=1;i<=Q;i++){
        cin>>q[i].u>>q[i].v>>q[i].a>>q[i].b;
        q[i].id=i;
    }
    sort(q+1,q+1+Q,cmp2);
    for(int i=1;i<=Q;i++){
        int k=upper_bound(b+1,b+1+siz,q[i].a)-b;
        k--;
        if(!k) ans[q[i].id]=0;
        else v[k].pb(i);
    }
    for(int i=1;i<=siz;i++){
        mem();
        int head=1;
        if(i!=1) sort(e+1,e+L[i],cmp2);
        for(int x:v[i]){
            while(head<=L[i]&&e[head].b<=q[x].b){
                add(e[head].u,e[head].v,e[head].a,e[head].b);
                head++;
            }
            top=0;
            for(int j=L[i];j<=R[i];j++){
                if(e[j].a<=q[x].a&&e[j].b<=q[x].b){
                    add(e[j].u,e[j].v,e[j].a,e[j].b);
                }
            }
            int u=find(q[x].u),v=find(q[x].v);
            if(u==v&&mxa[u]==q[x].a&&mxb[u]==q[x].b){
                ans[q[x].id]=1;
            }
            while(top) del(top--);
        }
    }
    for(int i=1;i<=Q;i++){
        if(ans[i]) cout<<"Yes"<<endl;
        else cout<<"No"<<endl;
    }
}
signed main(){
    // freopen(".in","r",stdin);
    // freopen(".out","w",stdout);
    // ios::sync_with_stdio(false),cin.tie(0);
    while(T--){
        solve();
    }
    return ~~(0^_^0);
}

总结

本题还算有迹可循。遇到暴力已经无法优化的时候,通常考虑数据结构或者转更暴力的想法:分块。离线排序+边加边询问 是常见的经典技巧,多积累经验才能成熟运用。

作者:ryder

出处:https://www.cnblogs.com/ryder/p/17406874.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   Ryder00  阅读(9)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
more_horiz
keyboard_arrow_up light_mode palette
选择主题
点击右上角即可分享
微信分享提示