zoukankan      html  css  js  c++  java
  • 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(msqrt{m}logm+qsqrt{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;
    }
    

      

     
  • 相关阅读:
    EF之Model First
    easyui报错 Cannot read property 'length' of null
    EF迁移报错An operation was scaffolded that may result in the loss of data. Please review the migration for accuracy.
    Vue脚手架搭建
    [LeetCode No.20] 有效的括号
    爬虫-给女朋友的每日天气预报
    [LeetCode No.316] 去除重复字母
    [LeetCode No.738] 单调递增的数字
    [LeetCode No.49] 字母异味词分组
    [LeetCode No.34] 在排序数组中查找元素的第一个和最后一个位置
  • 原文地址:https://www.cnblogs.com/ljh2000-jump/p/6409977.html
Copyright © 2011-2022 走看看