bzoj 4537: [Hnoi2016]最小公倍数 分块+并查集

题目大意:

给定一张n个点m条边的无向图,每条边有两种权.每次询问某两个点之间是否存在一条路径上的边的两种权的最大值分别等于给定值.
n,q <= 50000. m <= 100000

题解:

通过分析可以得到,我们能经过的所有的边的两种权一定均分别不大于给定的值.
把这些边称作可行边。那么我们把所有的可行边加入到图当中,然后判断询问的两个点是不是联通.
如果联通我们再进一步判断一下所有与其所在的联通块联通的所有边的两种边权的分别的最大值.

然后就是考虑如何快速统计出所有的边并将其加入到联通块中.
我们选择如下策略: 把所有的边按第一种权排序,然后分块.
每块内按照第二种权排序
事实证明上面划掉的方法直接TLE
我们考虑离线所有的操作,然后每次对第一种权在某一个块内的所有询问.
我们知道如果某个询问的第一种权在块i中,那么1 ~ i-`中的所有边的第一种权都符合条件.
所以这时候对1 ~ i-1块中的所有边按照第二种权排序.
如果我们处理询问的时候按照第二种权递增访问就可以做到线性统计了.
所以我们将所有此时应该处理的询问按照第二种权排序.
那么我们可以线性统计所有在1 ~ i-1块中的边完成所有此时的询问.

那么在第i个块中的呢 ?
直接暴力查询,然后维护可持久化并查集即可.
我们直接记录所有的操作,然后暴力撤销即可.

不要问我复杂度是多少,O(跑的过)

#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
#define rg register int
inline void read(int &x){
    x=0;char ch;bool flag = false;
    while(ch=getchar(),ch<'!');if(ch == '-') ch=getchar(),flag = true;
    while(x=10*x+ch-'0',ch=getchar(),ch>'!');if(flag) x=-x;
}
const int maxn = 100010;
const int maxm = 100010;
struct Edge{
    int id;
    int u,v,a,b;
}e[maxm],q[maxm],tmp[maxm];
int cnt;
inline bool cmpa(const Edge &a,const Edge &b){
    return a.a == b.a ? a.b < b.b : a.a < b.a;
}
inline bool cmpb(const Edge &a,const Edge &b){
    return a.b == b.b ? a.a < b.a : a.b < b.b;
}
int n,m;
struct Node{
    int idx,idy,fa,siz,mxa,mxb;
    Node(){}
    Node(const int &a,const int &b,const int &c
	 ,const int &d,const int &e,const int &f){
	 idx = a;idy = b;fa = c;siz = d;mxa = e;mxb = f;
    }
}sta[maxm];
int top;
int fa[maxn],siz[maxn],mxa[maxn],mxb[maxn];
inline void clear(){
    for(int i=1;i<=n;++i){
	fa[i] = i;siz[i] = 1;
	mxa[i] = mxb[i] = -1;
    }
}
inline int find(int x){
    while(x != fa[x]) x = fa[x];
    return x;
}
inline void Union(const Edge &e){
    int x = find(e.u),y = find(e.v);
    if(siz[x] > siz[y]) swap(x,y);
    sta[++top] = Node(x,y,fa[x],siz[y],mxa[y],mxb[y]);
    mxa[y] = max(mxa[y],max(mxa[x],e.a));
    mxb[y] = max(mxb[y],max(mxb[x],e.b));
    if(x == y) return ;
    siz[y] += siz[x];fa[x] = y;
}
inline void Undo(){
    while(top){
	fa[sta[top].idx] = sta[top].fa;
	siz[sta[top].idy] = sta[top].siz;
	mxa[sta[top].idy] = sta[top].mxa;
	mxb[sta[top].idy] = sta[top].mxb;
	-- top;
    }
}
bool ans[maxm];
int main(){
    read(n);read(m);
    for(int i=1;i<=m;++i){
        read(e[i].u);read(e[i].v);
        read(e[i].a);read(e[i].b);
    }
    int Q;read(Q);
    for(int i=1;i<=Q;++i){
        read(q[i].u);read(q[i].v);
        read(q[i].a);read(q[i].b);
        q[i].id = i;
    }
    sort(e+1,e+m+1,cmpa);sort(q+1,q+Q+1,cmpb);
    int block = sqrt(m);
    for(int i=1;i<=m;i+=block){
        cnt = 0;
        for(int j=1;j<=Q;++j){
            if(q[j].a >= e[i].a && (i+block > m || e[i+block].a > q[j].a)){
                tmp[++cnt] = q[j];
            }
        }
        sort(e+1,e+i,cmpb);clear();
        for(int j=1,k=1;j<=cnt;++j){
            while(k < i && e[k].b <= tmp[j].b) Union(e[k++]);
            top = 0;
            for(int l=i;l<i+block && l <= m;++l){
                if(e[l].a <= tmp[j].a && e[l].b <= tmp[j].b) Union(e[l]);
            }
            int x = find(tmp[j].u),y = find(tmp[j].v);
            if(x == y && mxa[x] == tmp[j].a && mxb[x] == tmp[j].b) ans[tmp[j].id] = true;
            Undo();
        }
    }
    for(int i=1;i<=Q;++i) puts(ans[i] ? "Yes" : "No");
    return 0;
}

其实复杂度是:\(O( m\sqrt{m}logm + q(logn+logq))\)

posted @ 2017-04-05 05:56  Sky_miner  阅读(246)  评论(0编辑  收藏  举报