【BZOJ4537】最小公倍数(HNOI2016)-分块+并查集启发式合并

测试地址:最小公倍数
做法:本题需要用到分块+并查集启发式合并。
脱去题目各种疑似数论的包装之后,我们发现实际上题目让你求的是:存不存在一条路径,使得路径上最大的a和最大的b分别为询问的AB。显然如果只有一个询问的话,我们就把aA并且bB的边连起来,再用并查集维护联通块内最大的a和最大的b,如果询问的两个点连通并且最大的ab分别等于AB,那就存在这样一条路径,否则就不存在。那么现在的问题是怎么把aA,bB的这些边取出来。
考虑两种暴力:
1.每次扫一遍所有的边,将所有aA,bB的边连起来,用并查集维护。
2.将边按a排序,将询问按B排序,每次处理一个询问时,将aA的边按b从小到大排序,处理对应的边。
我们将这两种暴力折中起来,成为一个分块算法,具体如下:
将边按a排序,将询问按B排序,然后将边分块,对于每一块,处理A在这一块内的询问,做法就是:对于前面所有块中的边,因为在前面的块中a必然A,那么这些边只要在询问的B到某个程度的时候是一定会被加入的,所以我们把这些边按b排序,这样就能保证前面块中的边在处理每一块时只会处理一次。对于块内的边,暴力扫一遍将符合条件的边全部处理。注意在处理一个新的询问之前,我们还要将一些操作撤销,所以并查集不能路径压缩,那么就必须使用启发式合并来保证时间复杂度,那么我们就得到了一个复杂度为O(mmlogn)的算法。实践证明(实际上是抄的题解),块大小为mlogn时是最优的。
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
int n,m,q,blocksiz,changed[50010],s[50010];
bool vis[50010]={0},ans[50010]={0};
struct node
{
    int fa,mxa,mxb,h;
}a[50010],ori[50010];
struct edge
{
    int u,v,a,b,id;
}e[100010],Q[50010];

void init()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
        scanf("%d%d%d%d",&e[i].u,&e[i].v,&e[i].a,&e[i].b);
    scanf("%d",&q);
    for(int i=1;i<=q;i++)
    {
        scanf("%d%d%d%d",&Q[i].u,&Q[i].v,&Q[i].a,&Q[i].b);
        Q[i].id=i;
    }
}

bool cmp(edge a,edge b)
{
    if (a.a!=b.a) return a.a<b.a;
    else return a.b<b.b;
}

bool cmpb(edge a,edge b)
{
    return a.b<b.b;
}

int find(int x)
{
    while(x!=a[x].fa) x=a[x].fa;
    return x;
}

void change(int x)
{
    vis[x]=1;
    changed[++changed[0]]=x;
    ori[x]=a[x];
}

void merge(int x,int y,int i,bool type)
{
    int fx=find(x),fy=find(y);
    if (a[fx].h>a[fy].h) swap(fx,fy);
    if (type&&!vis[fx]) change(fx);
    if (type&&!vis[fy]) change(fy);
    if (fx==fy)
    {
        a[fx].mxa=max(a[fx].mxa,e[i].a);
        a[fx].mxb=max(a[fx].mxb,e[i].b);
        return;
    }
    a[fx].fa=fy;
    a[fy].mxa=max(max(a[fx].mxa,a[fy].mxa),e[i].a);
    a[fy].mxb=max(max(a[fx].mxb,a[fy].mxb),e[i].b);
    a[fy].h=max(a[fy].h,a[fx].h+1);
}

void work()
{
    blocksiz=sqrt(m);
    sort(e+1,e+m+1,cmp);
    sort(Q+1,Q+q+1,cmpb);

    int top;
    for(int i=1;i<=m;i+=blocksiz)
    {
        for(int j=1;j<=n;j++)
        {
            a[j].fa=j;
            a[j].mxa=a[j].mxb=-1;
            a[j].h=1;
        }
        sort(e+1,e+i,cmpb);

        top=0;
        for(int j=1;j<=q;j++)
            if (Q[j].a>=e[i].a&&(i+blocksiz>m||Q[j].a<e[i+blocksiz].a))
                s[++top]=j;

        int nowp=1;
        for(int k=1;k<=top;k++)
        {
            int nowq=s[k];
            while(nowp<i&&e[nowp].b<=Q[nowq].b)
            {
                merge(e[nowp].u,e[nowp].v,nowp,0);
                nowp++;
            }
            changed[0]=0;
            for(int j=i;j<=min(i+blocksiz-1,m);j++)
                if (e[j].a<=Q[nowq].a&&e[j].b<=Q[nowq].b)
                    merge(e[j].u,e[j].v,j,1);

            int fx=find(Q[nowq].u),fy=find(Q[nowq].v);
            ans[Q[nowq].id]=(fx==fy&&a[fx].mxa==Q[nowq].a&&a[fx].mxb==Q[nowq].b);

            for(int j=1;j<=changed[0];j++)
            {
                a[changed[j]]=ori[changed[j]];
                vis[changed[j]]=0;
            }
        }
    }
}

void output()
{
    for(int i=1;i<=q;i++)
    {
        if (ans[i]) printf("Yes\n");
        else printf("No\n");
    }
}

int main()
{
    init();
    work();
    output();

    return 0;
}
posted @ 2018-05-02 17:56  Maxwei_wzj  阅读(138)  评论(0编辑  收藏  举报