[BZOJ4569][Scoi2016]萌萌哒(并查集+st表)

题目描述

传送门

题解

考场上没想出来,看了题解之后感觉很厉害呀。

可以发现相等的两个区间中互相对应的位置是联系在一起的,也就是说,确定了一个就可以确定另外一个。所以可以考虑把这样的点合并起来。考场上写了个比较傻逼的tarjan,其实并查集就是可以做的。无向图的tarjan实际上就是并查集。时间复杂度O(nm)

但是这样的话就有很多冗余的合并,因为区间有一些是重复的,所以有的点就被合并了很多次,考虑如何去除重复操作。可以用ST表来实现。f[j][i]的含义是 从i开始的2^j个字符分别和从f[j][i]开始的2^j个字符对应相等。
每次并查集合并f[j][x],f[j][y]的时候,要把f[j-1][x],f[j-1][y] 以及 f[j-1][x+(1<<(j-1))] f[j-1][y+(1<<(j-1)] 合并,这样自顶向下的合并过程中,如果之前已经合并过了就直接退出。

ST表可以表示一段区间,所以这样来合并区间的操作就很方便了。

代码

#include
#include
#include
#include
#include
using namespace std;
#define LL long long

const int N=1e5+5;
const int sz=18;
const int Mod=1e9+7;

int n,m,cnt;
int f[sz+5][N];
LL ans;

inline int find(int j,int x)
{
    if (x==f[j][x]) return x;
    f[j][x]=find(j,f[j][x]);
    return f[j][x];
}
inline void merge(int j,int x,int y)
{
    int f1=find(j,x),f2=find(j,y);
    if (f1==f2) return;
    f[j][f1]=f2;
    if (!j) return;
    merge(j-1,x,y); merge(j-1,x+(1<<(j-1)),y+(1<<(j-1)));//
}

int main()
{
    scanf("%d%d",&n,&m);
    if (n==1)
    {
        puts("10");
        return 0;
    }
    for (int j=0;jfor (int i=1;i<=n;++i)
            f[j][i]=i;
    int x1,y1,x2,y2;
    for (int i=1;i<=m;++i)
    {
        scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
        if (x1>x2) swap(x1,x2),swap(y1,y2);
        if (x1==x2) continue;
        int j=log2(y1-x1+1);
        merge(j,x1,x2); merge(j,y1-(1<1,y2-(1<1);
    }
    for (int i=1;i<=n;++i)
        if (find(0,i)==i) cnt++;
    LL ans=9;
    for (int i=1;i10)%Mod;
    printf("%lld\n",ans);
}

你可能感兴趣的:(题解,并查集,st表,省选)