bzoj4537: [Hnoi2016]最小公倍数

HNOI 同时被 3 个专栏收录
18 篇文章 0 订阅
3 篇文章 0 订阅
44 篇文章 0 订阅

题目链接

bzoj4537

题目描述

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

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

题解

转化一下就是问是否存在求一条路径使得a的最大值和b的最大值分别为给定的值。
我们将边权按a排序,每 m 分为1块。然后对于没有块,我们处理a在该块内的所有询问。假设我们现在处理第i块,我们先将第i块的询问及1至i-1块的边取出,按b排序。然后按顺序加入这些边,同时我们要用权值并查集维护图的连通性以及联通块中的a,b的最大值。对于一个询问,可能还存在一些合法的边在第i块中,但这些边不超过 m 条,我们可以暴力加入再暴力还原。
时间复杂度: O(mmlogm)


#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;

const int N=100010;
struct node{
    int x,y,a,b,id;
    friend bool operator <(const node &a,const node &b){
        if(a.a==b.a) return a.b<b.b;
        return a.a<b.a;
    }
    void init(int k){
        id=k; scanf("%d%d%d%d",&x,&y,&a,&b);
    }
}e[N],q[N],tmp[N];
struct opt{
    int x,y,f,da,db,sz;
}op[N];
int ans[N],f[N],da[N],db[N],sz[N],Q,n,m,top,tot,x,y,S;

bool cmp(const node &a,const node &b){
    if(a.b==b.b) return a.a<b.a;
    return a.b<b.b;
}
int find(int x){
    return f[x]==x?x:find(f[x]);
}
void merge(int x,int y,int a,int b){
    x=find(x); y=find(y);
    if(sz[x]>sz[y]) swap(x,y);
    op[++tot].x=x; op[tot].da=da[y]; op[tot].db=db[y];
    op[tot].y=y; op[tot].f=f[x]; op[tot].sz=sz[y];
    if(x==y){
        da[x]=max(da[x],a); 
        db[x]=max(db[x],b); 
        return;
    }
    f[x]=y; sz[y]+=sz[x];
    da[y]=max(da[y],max(da[x],a));
    db[y]=max(db[y],max(db[x],b));
}
void Return(){
    for(;tot;tot--){
        f[op[tot].x]=op[tot].f;
        db[op[tot].y]=op[tot].db;
        da[op[tot].y]=op[tot].da;
        sz[op[tot].y]=op[tot].sz;
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++) e[i].init(i);
    sort(e+1,e+1+m);
    S=(int)sqrt(m);
    scanf("%d",&Q);
    for(int i=1;i<=Q;i++) q[i].init(i);
    sort(q+1,q+1+Q,cmp);
    for(int i=1;i<=m;i+=S){
        top=0; 
        for(int j=1;j<=Q;j++)
        if(q[j].a>=e[i].a&&(i+S>m||q[j].a<e[i+S].a))
        tmp[++top]=q[j];
        sort(e+1,e+i,cmp);
        for(int j=1;j<=n;j++) f[j]=j,sz[j]=1,da[j]=db[j]=-1;
        for(int j=1,k=1;j<=top;j++){
            for(;k<i&&e[k].b<=tmp[j].b;k++)
            merge(e[k].x,e[k].y,e[k].a,e[k].b);
            tot=0;
            for(int l=i;l<i+S&&l<=m;l++)
            if(e[l].a<=tmp[j].a&&e[l].b<=tmp[j].b)
            merge(e[l].x,e[l].y,e[l].a,e[l].b);
            x=find(tmp[j].x); y=find(tmp[j].y);
            ans[tmp[j].id]=x==y&&da[x]==tmp[j].a&&db[x]==tmp[j].b;
            Return();
        }
    }
    for(int i=1;i<=Q;i++)
    if(ans[i]) puts("Yes");
    else puts("No");
    return 0;
}
展开阅读全文
  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值