• P3247 [HNOI2016]最小公倍数 操作分块


    题意:

    戳这里

    分析:

    • 暴力:

    每一次枚举符合要求的边,加入并查集,然后查询

    1. 两点是否在同一个并查集内
    2. (a) 的最大值是否等于要求
    3. (b) 的最大值是否等于要求

    复杂度 (O(qmlog))

    • 正解

    对于这种带有两种权值的问题,我们通常的做法就是,将一维排好序,对另一维进行查询

    而对于这个题来说,我们发现时间复杂度的瓶颈在于枚举边,所以我们考虑通过分块的方法优化

    具体做法就是:

    1. 对所有的边进行第一关键字升序排序,然后对询问按照每 (blo) 个分成一块

    2. 每次(blo) 条边处理一次,每次把符合要求的询问加入队列(符合要求指 询问的询问的第一关键字,大于第 (i) 条边的第一关键字,小于第 (i+blo) 条边的第一关键字)

    3. 这样保证了队列里的询问的第一关键字一定大于前 (i) 条边的第一关键字,即满足了第 (2) 条要求,然后我们对于前 (i) 条边重新按照第二关键字升序排序,由于队列里的询问满足第二关键字升序,所以我们可以双指针一边扫一边加入,即满足了第 (1) 条要求

    4. 由于队列里的每个询问的第一关键字并不有序所以我们不能直接利用原来的并查集,需要并查集支持可撤销

    至此此题的做法就完结了,但代码里有很多细节值得注意

    tip:

    1. 每次加入一条边的时候除了,将两个并查集合并以外,还要将这条边的影响加上

    2. 由于 (1) 的操作,所以并查集合并时,哪怕是同一个并查集里的点,也要将这个状态压进栈,因为可能存在边的影响

    代码:

    #include<bits/stdc++.h>
    #define pii pair<int,int>
    #define mk(x,y) make_pair(x,y)
    #define lc rt<<1
    #define rc rt<<1|1
    #define pb push_back
    #define fir first
    #define sec second
    
    using namespace std;
    
    namespace zzc
    {
    	inline int read()
    	{
    		int x=0,f=1;char ch=getchar();
    		while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
    		while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
    		return x*f;
    	}
    	
    	const int maxn = 1e5+5;
    	int n,m,qt,top,blo,cnt,now;
    	int fa[maxn],siz[maxn],val[maxn][2];
    	bool ans[maxn];
    	
    	struct node
    	{
    		int frm,to,a,b,k;
    		node(){}
    		node(int frm,int to,int a,int b,int k):frm(frm),to(to),a(a),b(b),k(k){}
    	}e[maxn],q[maxn],st[maxn],tmp[maxn];
    	
    	bool cmpa(node x,node y)
    	{
    		return x.a==y.a?x.b<y.b:x.a<y.a;
    	}
    	
    	bool cmpb(node x,node y)
    	{
    		return x.b==y.b?x.a<y.a:x.b<y.b;
    	}
    	
    	int find(int x){return x==fa[x]?x:find(fa[x]);}
    	
    	void merge(int x,int y,int a,int b)
    	{
    		int fx=find(x),fy=find(y);
    		if(siz[fx]<siz[fy]) swap(fx,fy);
    		st[++top]=node(fx,fy,val[fx][0],val[fx][1],siz[fx]);
    		if(fx!=fy)
    		{
    			siz[fx]+=siz[fy];fa[fy]=fx;
    			val[fx][0]=max(val[fx][0],val[fy][0]);
    			val[fx][1]=max(val[fx][1],val[fy][1]);
    		}
    		val[fx][0]=max(val[fx][0],a);
    		val[fx][1]=max(val[fx][1],b);
    	}
    	
    	void del()
    	{
    		while(top)
    		{
    			int x=st[top].to,fx=st[top].frm;
    			siz[fx]=st[top].k;
    			val[fx][0]=st[top].a;
    			val[fx][1]=st[top].b;
    			fa[x]=x;
    			top--;
    		}
    	}
    	
    	void work()
    	{
    		n=read();m=read();
    		for(int i=1;i<=m;i++) e[i].frm=read(),e[i].to=read(),e[i].a=read(),e[i].b=read();
    		qt=read();
    		for(int i=1;i<=qt;i++) q[i].frm=read(),q[i].to=read(),q[i].a=read(),q[i].b=read(),q[i].k=i;
    		sort(e+1,e+m+1,cmpa);sort(q+1,q+qt+1,cmpb);
    		blo=(int)sqrt(m*log2(n));
    		for(int i=1;i<=m;i+=blo)
    		{
    			cnt=0;now=1;
    			for(int j=1;j<=n;j++) fa[j]=j,siz[j]=1,val[j][0]=val[j][1]=-1;
    			for(int j=1;j<=qt;j++) if(q[j].a>=e[i].a&&(q[j].a<e[i+blo].a||i+blo>m)) tmp[++cnt]=q[j];
    			if(!cnt) continue;
    			sort(e+1,e+i,cmpb);
    			for(int j=1;j<=cnt;j++)
    			{
    				while(now<i&&e[now].b<=tmp[j].b) merge(e[now].frm,e[now].to,e[now].a,e[now].b),now++;
    				top=0;
    				for(int k=i;k<i+blo&&k<=m;++k) if(e[k].a<=tmp[j].a&&e[k].b<=tmp[j].b) merge(e[k].frm,e[k].to,e[k].a,e[k].b);
    				int fx=find(tmp[j].frm),fy=find(tmp[j].to);
    				ans[tmp[j].k]=(fx==fy&&val[fx][0]==tmp[j].a&&val[fx][1]==tmp[j].b);
    				del();
    			}
    		}
    		for(int i=1;i<=qt;i++) 
    		if(ans[i]) printf("Yes
    ");
    		else printf("No
    ");
    	}
    
    }
    
    int main()
    {
    	zzc::work();
    	return 0;
    }
    
    
  • 相关阅读:
    获取全部 txt 文本中出现次数最多的前N个词汇
    提取txt文本有效内容
    部分画图
    Series结构(常用)
    C 语言实例
    HTML之marquee(文字滚动)详解
    一款好看的404页面代码 | 滚动的404
    VS2010到VS2019各个版本的密钥
    什么是工程/项目?
    什么是IDE(集成开发环境)?
  • 原文地址:https://www.cnblogs.com/youth518/p/14197761.html
Copyright © 2020-2023  润新知