Loading

luoguP3247 [HNOI2016]最小公倍数 题解

题意

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

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

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

\(1\le n,q\le 5\times 10^4\)\(1\leq m\leq 10^5\)\(0\leq a,b\leq 10^9\)

思路

一步一步来。

暴力

先分析给定的条件:满足经过边边权的 \(\operatorname{lcm}\)\(2^a\times 3^b\)。那我们可以拆解成:每条边有 \(x\) 属性和 \(y\) 属性,所经过边的 \(x\) 属性最大值为 \(a\),且 \(a\) 必须出现过。\(y\) 属性同理。

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

正解

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

总结

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

posted @ 2023-05-16 21:19  Ryder00  阅读(25)  评论(0)    收藏  举报