BZOJ4537 [Hnoi2016]最小公倍数

本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作。

 

 

本文作者:ljh2000
作者博客:http://www.cnblogs.com/ljh2000-jump/
转载请注明出处,侵权必究,保留最终解释权!

 

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是一个连续区间。
  我每次处理一个块,处理这个块时,就把询问的a权值在这个区间的所有询问提出来处理。
  与此同时,我把这个块之前的(不包括这个块)所有块的所有边提出来,按b排序,依次加入集合考虑。
  考虑因为在这个块之前就意味着a对于我当前处理的所有询问一定是合法的,而此时边和询问都是按b排序的。
  所以我可以单调指针往后扫,每次往并查集中加入b不超过当前询问限制的所有边,然后check一下,询问的两个点是否在一个连通块内,且连通块内的边权a、b最大值是否跟我求的一致。
  但是上述做法还忽略了当前块的一些满足条件的边,我们可以对于每次询问暴力加入这一块中可以加入的所有边,再去check,做完这次询问再暴力恢复。
  考虑我一个块内最多只有sqrt(m)个元素,所以复杂度可以保证,但是我的并查集就必须写按秩合并了,方便每次恢复。
  
  上述做法就可以保证我查询的正确性了(有一个优化就是如果a大量相等,两个块之间就没必要重做了,可以留到下次),所以总复杂度:$O(m\sqrt{m}logm+q\sqrt{m})$。
 
 
//It is made by ljh2000
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <ctime>
#include <vector>
#include <queue>
#include <map>
#include <set>
#include <string>
#include <complex>
using namespace std;
typedef long long LL;
const int MAXQ = 100011;
const int MAXM = 200011;
const int MAXS = 10011;
int n,m,Q,L[MAXS],R[MAXS],block,kcnt,belong[MAXM],top;
int maxa[MAXM],maxb[MAXM],father[MAXM],size[MAXM],cnt,ans[MAXQ];

struct edge{ int x,y,a,b; }e[MAXM];
inline bool cmpa(edge q,edge qq){ return q.a<qq.a; }
inline bool cmpb(edge q,edge qq){ return q.b<qq.b; }

struct ask{ int x,y,a,b,id; }q[MAXQ],tmp[MAXQ];
inline bool cmp1(ask q,ask qq){ return q.a<qq.a; }
inline bool cmp2(ask q,ask qq){ return q.b<qq.b; }

struct revor{ int x,y,size,fa,a,b; }c[MAXM];
inline int find(int x){ while(father[x]!=x) x=father[x]; return father[x]; }

inline int getint(){
    int w=0,q=0; char c=getchar(); while((c<'0'||c>'9') && c!='-') c=getchar();
    if(c=='-') q=1,c=getchar(); while (c>='0'&&c<='9') w=w*10+c-'0',c=getchar(); return q?-w:w;
}

inline void add(int x,int y,int a,int b,int type){//type标记是否需要还原
	int r1=find(x),r2=find(y); if(size[r1]>size[r2]) swap(r1,r2);
	if(type==1) {
		c[++top].x=r1; c[top].y=r2;
		c[top].size=size[r2]; c[top].fa=r1; c[top].a=maxa[r2]; c[top].b=maxb[r2];//保存历史情况,便于恢复
	}
	if(r1==r2) maxa[r2]=max(maxa[r2],a),maxb[r2]=max(maxb[r2],b);
	else {
		father[r1]=r2;
		size[r2]+=size[r1];
		maxa[r2]=max(maxa[r2],max(maxa[r1],a));
		maxb[r2]=max(maxb[r2],max(maxb[r1],b));
	}
}

inline void work(){
	n=getint(); m=getint(); for(int i=1;i<=m;i++) e[i].x=getint(),e[i].y=getint(),e[i].a=getint(),e[i].b=getint();
	Q=getint(); for(int i=1;i<=Q;i++) q[i].x=getint(),q[i].y=getint(),q[i].a=getint(),q[i].b=getint(),q[i].id=i;
	sort(e+1,e+m+1,cmpa); sort(q+1,q+Q+1,cmp2);//边按a排序,询问按b排序

	block=(int)(sqrt(3*m)); kcnt=m/block; if(m%block) kcnt++;
	int x,y; for(int i=1;i<=kcnt;i++) L[i]=m+1; //L初值是m+1不是n+1!!!
	for(int i=1;i<=m;i++) belong[i]=(i-1)/block+1,L[belong[i]]=min(L[belong[i]],i),R[belong[i]]=i;

	for(int i=1;i<=kcnt;i++) {
		cnt=0;
		for(int j=1;j<=Q;j++) 
			//大量a相等的块会留到下次处理
			if(q[j].a>=e[L[i]].a && (R[i]==m || q[j].a<e[R[i]+1].a))//本次只处理询问的a处于当前块的所有询问
				tmp[++cnt]=q[j];

		sort(e+1,e+L[i],cmpb);
		for(int j=1;j<=n;j++) father[j]=j,maxa[j]=maxb[j]=-1,size[j]=1;//size需要初始化!初值要设为-1!

		for(int j=1,k=0;j<=cnt;j++) {
			while(k+1<L[i] && e[k+1].b<=tmp[j].b) { k++; add(e[k].x,e[k].y,e[k].a,e[k].b,0); }//加入之前所有块中满足条件的所有边
			for(int ii=L[i];ii<=R[i];ii++) //暴力加入这个块中的部分
				if(e[ii].a<=tmp[j].a && e[ii].b<=tmp[j].b)
					add(e[ii].x,e[ii].y,e[ii].a,e[ii].b,1);

			x=tmp[j].x; y=tmp[j].y;	x=find(x); y=find(y);
			ans[tmp[j].id]=( (x==y) && (maxa[x]==tmp[j].a && maxb[x]==tmp[j].b) );

			while(top>0) {//暴力消除这个块的部分的影响
				x=c[top].x; y=c[top].y;
				father[x]=c[top].fa;
				size[y]=c[top].size;
				maxa[y]=c[top].a;
				maxb[y]=c[top].b;
				top--;
			}
		}
	}
	for(int i=1;i<=Q;i++) if(ans[i]) puts("Yes"); else puts("No");
}

int main()
{
    work();
    return 0;
}

  

 
posted @ 2017-02-17 15:14  ljh_2000  阅读(217)  评论(0编辑  收藏  举报