BZOJ4537[HNOI2016]最小公倍数

那个,事情是这样的,从前有一只蒟蒻(对,就是我),然后有一天他心血来潮想创个博客。。

可是没过多久,他的热情就过去了,甚至连一篇博文都没有写。。

然后,不知道怎么了,他的博客就被机房里的dalao翻出来了,还被嘲讽了一番。。

于是他决定补救一下他的博客。。嗯,就这样。。


这道题大概是这样的。。给定一张n个点m条边的带权无向图,图上的边权都是的格式

然后给定q个询问,询问有四个参数u,v,a,b,问是否存在一条从u到v的路径使得路径上所有边权的最小公倍数为(不一定要是简单路径)

首先,转化一下题目。。最小公倍数的部分相当于路径上最大的a值和b值等于询问的a值和b值

然后又不一定需要是简单路径,所以所求路径等价于包含u和v的一个连通块

对于前20%的数据,暴力算法十分的明显,对于每个询问i,维护一个并查集,只加入的边

最后只要满足f[u]==f[v]且并查集内最大的a值和b值等于ai和bi则符合题意,然而这个算法只能获得20%的分数

考虑用分块优化这个暴力,将所有的边按a排序

对于第i个块,将a值在块的值的范围内的询问取出来

将前i-1个块的边和取出来的询问按b排序,由于所有的a满足要求,b的值为递增的,将边按顺序加入并查集,判断一下即可。

考虑对答案有贡献的边有可能在第i个块内,且询问的a并不是递增的,所以需要对于每个询问,将第i个块内的边依次加入,判断后删除即可,这样的边最多条。。需要并查集支持回溯功能,这个只需按秩合并然后开个栈存一下加边前的状态就好了

时间复杂度为,由于是第一次做分块的题,没什么就经验,直接用sqrt(m)做块的大小。。T了好几次,最后参考了神犇的程序,把块的大小换成常数就A了QAQ

#include<cmath>
#include<cstdio>
#include<algorithm>
using std::sort;
const int N=50005;
inline int read()
{
	int t=0,c=getchar();
	for(;c<48||c>57;c=getchar());
	do
	  {
	  	t=(t<<1)+(t<<3)+c-48;
	  	c=getchar();
	  }
	while(47<c&&c<58);
	return t;
}
struct data
{
	int u,v,a,b,id;
	void init(int i)
	  {
	  	id=i;u=read();v=read();
	  	a=read();b=read();
	  }
}q[N],e[N<<1],st[N];
struct lx{int u,v,a,b,s;}z[N<<1];
int n,m,Q,S,tot,top,u,v,f[N],a[N],b[N],s[N];bool ans[N];
inline bool cmp(data a,data b)
{
	if(a.a==b.a)return a.b<b.b;
	return a.a<b.a;
}
inline bool cmp2(data a,data b)
{
	if(a.b==b.b)return a.a<b.a;
	return a.b<b.b;
}
inline int max(int a,int b){a-=b;return b+(a&(~a>>31));}
inline void swap(int &a,int &b){a^=b;b^=a;a^=b;}
inline int get(int x){return f[x]==x?x:get(f[x]);}
inline void merge(int u,int v,int A,int B)
{
	u=get(u);v=get(v);
	if(s[v]<s[u])swap(u,v);
	z[++tot]=(lx){u,v,a[v],b[v],s[v]};
	if(u==v)
	  {
	  	a[u]=max(a[u],A);
	  	b[u]=max(b[u],B);
	  	return;
	  }
	f[u]=v;s[v]+=s[u];
	a[v]=max(a[v],max(a[u],A));
	b[v]=max(b[v],max(b[u],B));
}
inline void back()
{
	int x,y;
	for(;tot;--tot)
	  {
	  	f[x=z[tot].u]=x;
	  	a[y=z[tot].v]=z[tot].a;
	  	b[y]=z[tot].b;
	  	s[y]=z[tot].s;
	  }
}
int main()
{
	n=read();m=read();
	for(int i=1;i<=m;++i)e[i].init(i);
	sort(e+1,e+m+1,cmp);scanf("%d",&Q);
	for(int i=1;i<=Q;++i)q[i].init(i);
	sort(q+1,q+Q+1,cmp2);S=755;
	for(int i=1,x;i<=m;i+=S)
	  {
	  	top=0;x=i+S;
	  	for(int j=1;j<=Q;++j)
	  	  if(q[j].a>=e[i].a&&(x>m||q[j].a<e[x].a))
	  	    st[++top]=q[j];
	  	sort(e+1,e+i,cmp2);
	  	for(int j=1;j<=n;++j)f[j]=j,s[j]=1,a[j]=b[j]=-1;
	  	for(int j=1,k=1;j<=top;++j)
	  	  {
	  	  	for(;k<i&&e[k].b<=st[j].b;++k)
	  	  	  merge(e[k].u,e[k].v,e[k].a,e[k].b);
	  	  	tot=0;
	  	  	for(int l=i;l<x&&l<=m;++l)
	  	  	  if(e[l].a<=st[j].a&&e[l].b<=st[j].b)
	  	  	    merge(e[l].u,e[l].v,e[l].a,e[l].b);
	  	  	u=get(st[j].u);v=get(st[j].v);
	  	  	ans[st[j].id]=(u==v&&a[u]==st[j].a&&b[u]==st[j].b);
	  	  	back();
		  }
	  }
	for(int i=1;i<=Q;++i)
	  if(ans[i])printf("Yes\n");
	  else printf("No\n");
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值