【bzoj4537】【HNOI2016】【最小公倍数】【并查集+启发式合并+分块】

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

题解:

考虑朴素做法,对于每个询问,把所有a,b值小于等于给定a,b的边合并.

顺便维护最大值,最后询问两点是否联通已经最大值是否正确即可.

这个显然可以用并查集来处理.

然后我们可以把边以a为第一关键字,b为第二关键字排序.再将边分块.

依次处理每个块,假设当前处理的块是x.

我们就处理a在这个块里的询问.把这些询问按b排序.

先把块1-(x-1)里符合条件的边加入.我们也可以对这些边按b排序,这样每条边只会加一次.

在块里也会有一些符合条件的边,我们暴力枚举这些边进行添加.

这些操作依然可以用并查集来处理,注意暴力枚举完我们还要暴力把并查集复原.

并查集不能路径压缩,使用启发式合并即可.

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define N 50010
#define M 100010
using namespace std;
int n,m,fa[N],ans[N],bk,size[N],Q,ma[N],mb[N],cnt(0),num;
struct use{int x,y,a,b,id;}e[M],q[N],st[N];
struct aa{int f,x,y,size,ma,mb;}h[M];
int read(){
   int x(0);char ch=getchar();
   while (ch<'0'||ch>'9') ch=getchar();
   while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
   return x; 
}
bool cmp1(use a,use b){
  if (a.a==b.a) return a.b<b.b;
  else return a.a<b.a;   
}
bool cmp2(use a,use b){
  if (a.b==b.b) return a.a<b.a;
  else return a.b<b.b;
}
int find(int x){
  if (x==fa[x]) return x;else return find(fa[x]);
}
void merge(int x,int y,int a,int b){
  int r1=find(x),r2=find(y);
  if (size[r1]>size[r2]) swap(r1,r2);
  h[++num].x=r1;h[num].y=r2;h[num].f=fa[r1];
  h[num].size=size[r2];h[num].ma=ma[r2];h[num].mb=mb[r2];
  if (r1==r2){
    ma[r1]=max(ma[r1],a);
    mb[r1]=max(mb[r1],b);
  }
  else{
    fa[r1]=r2;size[r2]+=size[r1];
    ma[r2]=max(ma[r2],max(ma[r1],a));
    mb[r2]=max(mb[r2],max(mb[r1],b));
  }
}
void recovery(){
  for (int i=num;i;i--){
    fa[h[i].x]=h[i].f;
    ma[h[i].y]=h[i].ma;
    mb[h[i].y]=h[i].mb;
    size[h[i].y]=h[i].size;
  } 
}
int main(){
  n=read();m=read();
  for (int i=1;i<=m;i++)
    e[i].x=read(),e[i].y=read(),e[i].a=read(),e[i].b=read();
  sort(e+1,e+m+1,cmp1);
  bk=sqrt(m*log2(m));scanf("%d",&Q);
  for (int i=1;i<=Q;i++)
    q[i].x=read(),q[i].y=read(),q[i].a=read(),q[i].b=read(),q[i].id=i;
  sort(q+1,q+Q+1,cmp2);
  for (int i=1;i<=m;i+=bk){
    cnt=0;
    for (int j=1;j<=Q;j++)
      if (q[j].a>=e[i].a&&(i+bk>m||q[j].a<e[i+bk].a))
        st[++cnt]=q[j];
    sort(e+1,e+i,cmp2);
    for (int j=1;j<=n;j++) fa[j]=j,ma[j]=mb[j]=-1,size[j]=1;
    int tot=1;
    for (int j=1;j<=cnt;j++){
      for(;tot<i&&e[tot].b<=st[j].b;tot++)
        merge(e[tot].x,e[tot].y,e[tot].a,e[tot].b);
      num=0;
      for (int k=i;k<i+bk&&k<=m;k++)
        if (e[k].a<=st[j].a&&e[k].b<=st[j].b)
          merge(e[k].x,e[k].y,e[k].a,e[k].b);
      int r1=find(st[j].x),r2=find(st[j].y);
      if (r1==r2&&ma[r1]==st[j].a&&mb[r1]==st[j].b) ans[st[j].id]=1;
      else ans[st[j].id]=0;
      recovery();
    }
  }
  for (int i=1;i<=Q;i++) 
    if (ans[i]) puts("Yes");
    else puts("No");
} 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值