P3247 [HNOI2016] 最小公倍数 解题报告

前置知识:可撤销并查集

用一个栈记录合并顺序,每次撤销将栈顶的元素恢复

但是这种方法不能路径压缩,因为会改变节点之间的关系,为了保证时间,可以按照 size 进行合并


题意显然为能不能找到一条路径,使这条路径上最大的 aqa,最大的 bqb

因为有a和b两个限制,考虑按照a和b的大小分块

因为数的范围很大,所以可以按照先将边按a排序后的位置对应的值分块

对于每一段,记最小值为 mina,最大值为maxa

先将满足 minaqamaxa 的询问加入s中

接下来考虑b的限制,因为此时a的问题已经解决了,所以可以对 amina的边按b从小到大排序

为了便于边的插入和删除,可以先将询问按b从小到大排序

此时,依次枚举s中的询问,加入满足 amina,bqb 的边,因为b是递增的,所以这些边加入后始终满足条件,不用删除

对于 minaqamaxa 的边,因为不一定满足条件,所以要利用可撤销并查集,对于每个询问,只加入符合条件的边,用完后撤销

此时,如果所求的u和v在同一个连通块,且块内最大的a和b均与所求相等,则答案为Yes,否则为No

代码:

#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
int n,m,q,blen,cnt,ans[50005];
struct node{
	int u,v,a,b,id;
}e[100005],qs[50005],s[50005];
bool cmp1(node x,node y)
{
	if(x.a==y.a) return x.b<y.b;
	return x.a<y.a;
}
bool cmp2(node x,node y)
{
	if(x.b==y.b) return x.a<y.a;
	return x.b<y.b;
}
int f[500005],siz[100005],maxa[100005],maxb[100005],top;
int find(int x)
{
	if(f[x]!=x) return find(f[x]);
	return x;
}
struct node1{
	int x,y,maxa,maxb,siz,fa;
}st[100005];
void merge(int u,int v,int a,int b)
{
	int x=find(u),y=find(v);
	if(siz[x]<siz[y])
	{
		swap(x,y),swap(u,v);
	}
	top++;
	st[top]=(node1){x,y,maxa[x],maxb[x],siz[x],f[y]};
	if(x==y)
	{
		maxa[x]=max(maxa[x],a);
		maxb[x]=max(maxb[x],b);
		return;
	}
	f[y]=x;
	siz[x]+=siz[y];
	maxa[x]=max(maxa[x],max(maxa[y],a));
	maxb[x]=max(maxb[x],max(maxb[y],b));
}
void split()
{
	while(top)
	{
		siz[st[top].x]=st[top].siz;
		maxa[st[top].x]=st[top].maxa;
		maxb[st[top].x]=st[top].maxb;
		f[st[top].y]=st[top].fa;
		top--;
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		int u,v,a,b;
		scanf("%d%d%d%d",&u,&v,&a,&b);
		e[i]=(node){u,v,a,b,i};
	}
	scanf("%d",&q);
	for(int i=1;i<=q;i++)
	{
		int u,v,a,b;
		scanf("%d%d%d%d",&u,&v,&a,&b);
		qs[i]=(node){u,v,a,b,i};
	}
	sort(e+1,e+m+1,cmp1);
	sort(qs+1,qs+q+1,cmp2);
	blen=sqrt(m);
	for(int i=1;i<=m;i+=blen)
	{
		cnt=0;
		for(int j=1;j<=q;j++)
		{
			if(qs[j].a>=e[i].a&&(i+blen>m||qs[j].a<e[i+blen].a))
			{
				s[++cnt]=qs[j];
			}
		}
		sort(e+1,e+i+1,cmp2);
		for(int j=1;j<=n;j++)
		{
			f[j]=j,siz[j]=1;
			maxa[j]=maxb[j]=-1;
		}
		for(int j=1,k=1;j<=cnt;j++)
		{
			while(k<i&&e[k].b<=s[j].b)
			{
				merge(e[k].u,e[k].v,e[k].a,e[k].b);
				k++;
			}
			top=0;
			for(int l=i;l<i+blen&&l<=m;l++)
			{
				if(e[l].a<=s[j].a&&e[l].b<=s[j].b)
				{
					merge(e[l].u,e[l].v,e[l].a,e[l].b);
				}
			}
			int x=find(s[j].u),y=find(s[j].v);
			if(x==y&&maxa[x]==s[j].a&&maxb[x]==s[j].b) ans[s[j].id]=1;
			split();
		}
	}
	for(int i=1;i<=q;i++)
	{
		if(ans[i]) printf("Yes\n");
		else printf("No\n");
	}
	return 0;
}
posted @   wangsiqi2010916  阅读(11)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示