• 【BZOJ4537】[HNOI2016] 最小公倍数(分块+并查集)


    点此看题面

    大致题意: 给定一张无向图,每条边有两种边权。每次询问两点间是否存在一条路径(可重复经过同一点、边),使得两种边权的最大值分别等于给出值。

    前言

    最近果然码力不够,写分块都能调上一个小时。。。

    洛谷上过了,结果黑暗爆炸OJ上(RE)了,而且不知道为什么BZOJ上也过不去(交上去玄学秒(T))。

    并查集

    反正对于两个限制的东西,一般都很容易想到在保证一个限制满足的情况下去搞第二个限制。

    显然根据题意,若我们把所有边权小于等于给出值的边连上,只要询问的两点连通且所在连通块的最大边权等于给出值,就存在合法路径。

    连通性、连通块信息,想到了什么?当然是并查集!

    所以我们就要考虑,如何对于一个询问,快速求出该情况下的并查集。

    分块

    考虑对询问按第一种权值排序,对于边按第二种边权排序分块。对于第(i)个块维护一个并查集表示前(i)个块的情况,一开始全部初始化为空。

    枚举询问,每次把第一种边权小于等于当前询问的边加入到它所在的块及之后块的并查集中。

    然后考虑怎么询问,显然根据我们的做法,第一种权值必然是符合限制的。

    因此,我们找到第二种权值所在的块,则先前所有块的情况我们已经维护好了,且必然都是满足限制的。所以只要加上这个不完整的块中符合限制的边,就可以求出当前情况的并查集了。

    然后每次处理完询问之后再撤销,这道题就做完了。

    代码

    #pragma GCC optimize(2)
    #pragma GCC optimize("inline")
    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 50000
    #define M 100000
    #define S 1500
    #define Gmax(x,y) (x<(y)&&(x=(y)))
    #define swap(x,y) (x^=y^=x^=y)
    using namespace std;
    int n,m,Q,sz,bl[M+5],g[M+5];bool ans[N+5];
    struct Qry {int p,x,y,a,b;I bool operator < (Con Qry& o) Con {return a<o.a;}}q[N+5];//询问按第一种权值排序
    struct edge {int x,y,a,b;I bool operator < (Con edge& o) Con {return b<o.b;}}e[M+5];//边按第二种权值排序
    I bool cmp(CI x,CI y) {return e[x].a<e[y].a;}//边按第一种权值排序,以便加边
    class FastIO
    {
    	private:
    		#define FS 100000
    		#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
    		#define D isdigit(c=tc())
    		char c,*A,*B,FI[FS];
    	public:
    		I FastIO() {A=B=FI;}
    		Tp I void read(Ty& x) {x=0;W(!D);W(x=(x<<3)+(x<<1)+(c&15),D);}
    		Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
    }F;
    int T,Sx[S+5],Sy[S+5],Sa[S+5],Sb[S+5],Sg[S+5];class UnionFindSet//并查集,栈用于撤销
    {
    	private:
    		unsigned f[N+5],g[N+5];int A[N+5],B[N+5];I int fa(CI x) {return f[x]?fa(f[x]):x;}
    	public:
    		I void Init() {for(RI i=1;i<=n;++i) A[i]=B[i]=-1;}//初始化
    		I void UForever(RI x,RI y,CI a,CI b)//加永久边
    		{
    			if((x=fa(x))==(y=fa(y))) goto F5;g[x]<g[y]&&swap(x,y),g[f[y]=x]+=g[x]==g[y];
    			F5:Gmax(A[x],A[y]),Gmax(A[x],a),Gmax(B[x],B[y]),Gmax(B[x],b);
    		}
    		I void UNow(RI x,RI y,CI a,CI b)//加临时边,存入栈中
    		{
    			if(++T,(x=fa(x))==(y=fa(y))) {Sx[T]=x,Sy[T]=Sg[T]=0;goto F5;}
    			g[x]<g[y]&&swap(x,y),Sx[T]=x,Sy[T]=y,g[f[y]=x]+=(Sg[T]=g[x]==g[y]);
    			F5:Sa[T]=A[x],Sb[T]=B[x],Gmax(A[x],A[y]),Gmax(A[x],a),Gmax(B[x],B[y]),Gmax(B[x],b);
    		}
    		I bool Qry(RI x,CI y,CI a,CI b)//处理询问
    		{
    			return (x=fa(x))==fa(y)&&A[x]==a&&B[x]==b;//连通且连通块最大值等于给出值
    		}
    		I void Revoke()//撤销
    		{
    			W(T) g[Sx[T]]-=Sg[T],A[Sx[T]]=Sa[T],B[Sx[T]]=Sb[T],f[Sy[T--]]=0;//清空栈
    		}
    }U[S+5];
    I void Add(CI x) {for(RI i=bl[x];i<=bl[m];++i) U[i].UForever(e[x].x,e[x].y,e[x].a,e[x].b);}//加边,加给它及其之后的连通块
    int main()
    {
    	RI i,j;for(F.read(n,m),i=1;i<=m;++i) F.read(e[i].x,e[i].y,e[i].a,e[i].b);sort(e+1,e+m+1);
    	for(F.read(Q),i=1;i<=Q;++i) q[i].p=i,F.read(q[i].x,q[i].y,q[i].a,q[i].b);sort(q+1,q+Q+1);
    	for(sz=sqrt(m*log2(n)),i=0;i<=m;++i) bl[g[i]=i]=(i-1)/sz+1;sort(g+1,g+m+1,cmp);//分块
    	RI k,p=1;for(i=0;i<=bl[m];++i) U[i].Init();for(i=1;i<=Q;++i)
    	{
    		W(p<=m&&e[g[p]].a<=q[i].a) Add(g[p++]);//加入第一种边权小于等于当前询问的边
    		k=upper_bound(e+1,e+m+1,(edge){0,0,0,q[i].b})-e-1;//找出第二种权值所在块
    		for(j=(bl[k]-1)*sz+1;j<=k;++j)//枚举不完整块
    			e[j].a<=q[i].a&&(U[bl[k]-1].UNow(e[j].x,e[j].y,e[j].a,e[j].b),0);//连上临时边
    		ans[q[i].p]=U[bl[k]-1].Qry(q[i].x,q[i].y,q[i].a,q[i].b),U[bl[k]-1].Revoke();//询问,然后撤销
    	}for(i=1;i<=Q;++i) puts(ans[i]?"Yes":"No");return 0;
    }
    
  • 相关阅读:
    MySQL Binlog解析(2)
    在线修改GTID模式
    官方online ddl
    pt-osc原理
    pt-osc使用方法
    python基本数据类型
    第一句python
    搭建私有云kodexplorer
    frp搭建
    Linux下快速分析DUMP文件
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/BZOJ4537.html
Copyright © 2020-2023  润新知