BZOJ 4537: [Hnoi2016]最小公倍数 分块 带权可撤销并查集

title

BZOJ 4537

LUOGU 3247

Description

给定一张 \(N\) 个顶点 \(M\) 条边的无向图(顶点编号为 \(1,2,…,n\) ),每条边上带有权值。所有权值都可以分解成 \(2^a*3^b\) 的形式。

现在有 \(q\) 个询问,每次询问给定四个参数 \(u、v、a、b\) ,请你求出是否存在一条顶点 \(u\)\(v\) 之间的路径,使得路径依次经过的边上的权值的最小公倍数为 \(2^a*3^b\)

注意:路径可以不是简单路径。

下面是一些可能有用的定义:

最小公倍数: \(K\) 个数 \(a_1,a_2,…,a_k\) 的最小公倍数是能被每个 \(a_i\) 整除的最小正整数。

路径:路径 \(P:P_1,P_2,…,P_k\) 是顶点序列,满足对于任意 \(1\leqslant i<k\) ,节点 \(P_i\)\(P_{i+1}\) 之间都有边相连。

简单路径:如果路径 \(P:P_1,P_2,…,P_k\) 中,对于任意 \(1\leqslant s\not=t\leqslant k\) 都有 \(P_s\not=P_t\) ,那么称路径为简单路径。

Input

输入文件的第一行包含两个整数 \(N\)\(M\) ,分别代表图的顶点数和边数。

接下来 \(M\) 行,每行包含四个整数 \(u、v、a、b\) 代表一条顶点 \(u\)\(v\) 之间、权值为 \(2^a*3^b\) 的边。

接下来一行包含一个整数 \(q\) ,代表询问数。接下来 \(q\) 行,每行包含四个整数 \(u、v、a、b\) ,代表一次询问。

询问内容请参见问题描述。\(1\leqslant n,q\leqslant 50000、1\leqslant m\leqslant 100000、0\leqslant a,b\leqslant 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

analysis

\(a\) 从小到大排序,然后分块。

把每个询问搞到 \(q_a\) 被第 \(i\) 个块所有的边的 \(a_e\) 构成的区间包含的块中(如有多个,搞到最后一个)

然后把在 \(i\) 之前的所有边和当前区间中搞的询问按照 \(b\) 从小到大排序。

然后从前往后一次处理操作,如果有边就加入到并查集中(维护连通性,以及每个连通块中 \(a\)\(b\) 的最大值)。

如果是询问的话,暴力将当前块内合法的边(也就是两维都不超过当前询问)加入即可,然后最后记得需要把所有加入的边撤回。

所以这个并查集不能路径压缩,我真是个傻子,都看到了带权可撤销并查集了,结果还是顺手写了个路径压缩,呜呜。

块的大小是个问题,因为我们要在块内进行过多的 \(sort\) ,可能导致常数巨大,然后...

大概把块的大小调成 \(\sqrt{n\log n}\) 还是挺可以的,复杂度瞎算 \(O(n\sqrt{n\log n})\) ,为啥说是瞎算,这里把 \(n,m\) 当同级来想了(好像也不是这个原因丫),然而好像之前见到有人说:实测,\(O(n\sqrt{n\log n})\) 不一定能跑得过 \(O(n\sqrt{n}\log n)\) ,这个,我就不太了解了(纯当我瞎说了),毕竟这题的复杂度跟块的大小太有关系了 。

code

#include<bits/stdc++.h>

const int maxn=1e5+1e3;
typedef int iarr[maxn];

namespace IO
{
    char buf[1<<15],*fs,*ft;
    inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; }
    template<typename T>inline void read(T &x)
    {
        x=0;
        T f=1, ch=getchar();
        while (!isdigit(ch) && ch^'-') ch=getchar();
        if (ch=='-') f=-1, ch=getchar();
        while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
        x*=f;
    }
}

using IO::read;

template<typename T>inline bool chkMin(T &a,const T &b) { return a>b ? (a=b, true) : false; }
template<typename T>inline bool chkMax(T &a,const T &b) { return a<b ? (a=b, true) : false; }
template<typename T>inline T min(T a,T b) { return a<b ? a : b; }
template<typename T>inline T max(T a,T b) { return a>b ? a : b; }

struct Orz
{
    int x,y,a,b,k;
    inline void Get(int k_=0)
    {
        read(x), read(y), read(a), read(b), k=k_;
    }
} E[maxn], Q[maxn], ask[maxn];

inline bool cmpa(Orz x,Orz y) { return x.a<y.a || (x.a==y.a && x.b<y.b); }
inline bool cmpb(Orz x,Orz y) { return x.b<y.b || (x.b==y.b && x.a<y.a); }

namespace Union_Set
{
    Orz Stack[maxn];
    iarr mxa,mxb,height,fa; int top;
    inline int get(int x)
    {
        return fa[x]==x ? x : get(fa[x]);//并查集不能路径压缩,否则无法撤销回去
    }

    inline Orz merge(int x,int y,int a,int b)
    {
        x=get(x), y=get(y);
        if (height[x]>height[y]) std::swap(x,y);
        Orz tmp=(Orz){x,y,mxa[y],mxb[y],height[y]};

        if (height[x]==height[y]) ++height[y];
        fa[x]=y;
        chkMax(mxa[y],max(mxa[x],a));
        chkMax(mxb[y],max(mxb[x],b));
        return tmp;
    }

    inline void del()
    {
        Orz cur=Stack[top--];
        fa[cur.x]=cur.x, mxa[cur.y]=cur.a, mxb[cur.y]=cur.b, height[cur.y]=cur.k;
    }
}

iarr Beg,End, Mina,Maxa, bel,id,ans;
int main()
{
    int n,m;read(n);read(m);
    int block=(int)(sqrt(n*log2(m)))*0.6;

    for (int i=1; i<=m; ++i) E[i].Get();
    std::sort(E+1,E+m+1,cmpa);

    for (int i=1; i<=m; ++i)
    {
        id[i]=i/block+1;
        if (id[i]!=id[i-1]) Mina[id[i]]=Maxa[id[i]]=E[i].a, Beg[id[i]]=i, End[id[i-1]]=i-1;
        else chkMin(Mina[id[i]],E[i].a), chkMax(Maxa[id[i]],E[i].a);
    }
    End[id[m]]=m;

    int q;read(q);
    for (int i=1; i<=q; ++i)
    {
        Q[i].Get(i);
        for (int j=id[m]; j>=1; --j)
            if (Mina[j]<=Q[i].a && Q[i].a<=Maxa[j]) { bel[i]=j; break; }
    }

    using namespace Union_Set;

    for (int i=1; i<=m; ++i) if (id[i]!=id[i-1])
    {
        int cnt=0;
        for (int j=1; j<=q; ++j)
            if (bel[j]==id[i]) ask[++cnt]=Q[j];
        if (!cnt) continue;

        std::sort(E+1,E+i,cmpb);
        std::sort(ask+1,ask+cnt+1,cmpb);

        for (int j=1; j<=n; ++j) fa[j]=j, mxa[j]=mxb[j]=-1;//初始化,因为 0 个数的 lcm 不是 1,所以初值为 -1
        int L=1, R=1;
        while ( R<i || L<=cnt)
        {
            if ( (R<i && ask[L].b>=E[R].b) || L>cnt) merge(E[R].x,E[R].y,E[R].a,E[R].b), ++R;
            else
            {
                for (int j=Beg[id[i]]; j<=End[id[i]]; ++j)
                    if (ask[L].b>=E[j].b && ask[L].a>=E[j].a) Stack[++top]=merge(E[j].x,E[j].y,E[j].a,E[j].b);

                int x=get(ask[L].x);
                if (x==get(ask[L].y)) ans[ask[L].k]=mxa[x]==ask[L].a && mxb[x]==ask[L].b;
                while (top) del();
                ++L;//Wrong Reason
            }
        }
    }

    for (int i=1; i<=q; ++i) puts(ans[i] ? "Yes" : "No");
    return 0;
}

summary

对于求两位偏序需要满足条件的题,常常可以分块然后配合可撤销数据结构来解决。

然后可以调整块大小优化复杂度?

然而,这其实不算是我的总结,不过也是我的学习了,见 队爷 \(blog\)

转载于:https://www.cnblogs.com/G-hsm/p/11461247.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值