BZOJ 4569: [Scoi2016]萌萌哒 [并查集 倍增]

传送门

题意:长为$n \le 10^5$的数字,给出$m \le 10^5$个限制$[l1,r1]\ [l2,r2]$两个子串完全相等,求方案数


 

把所有要求相等的位置连起来,不就是$9*10^{连通块个数}$嘛

但是最坏情况要连$nm$次啊

有很多都是重复的太浪费了

于是各种乱搞,甚至想了一下分块,即使能减少边的条数也不能减少计算时会走的边的次数

然后看题解,竟然是用倍增来优化

有道理啊,分块太死板了,倍增的话就可以灵活的得到每个区间

$fa[i][j]$表示从i开始$2^j$个数的区间的父亲(也就是和$fa[i][j]$开始$2^j$个数完全相等)

合并的时候从高层往低层合并(j大到小),遇到fa相同就停下

 

貌似一次合并最坏也是$O(n)$啊

这里卡了我好久. 每层最多合并$n$次,一共$logn$层,没问题啊

 

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=1e5+5, P=1e9+7;
typedef long long ll;
inline int read(){
    char c=getchar();int x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}

int n, m, l1, r1, l2, r2;
int fa[N][18], Log[N];
int find(int i, int j) {return i==fa[i][j] ? i : fa[i][j]=find(fa[i][j], j);}
void Union(int x, int y, int j) {
    int f1=find(x, j), f2=find(y, j);
    if(f1==f2) return;
    fa[f1][j]=f2;
    if(j) Union(x, y, j-1), Union(x + (1<<(j-1)), y+ (1<<(j-1)), j-1);
}
ll Pow(ll a, int b) {
    ll ans=1;
    for(; b; b>>=1, a=a*a%P)
        if(b&1) ans=ans*a%P;
    return ans;
}

int main() {
    freopen("in","r",stdin);
    n=read(); m=read();
    if(n==1) {puts("10"); return 0;}
    Log[1]=0; 
    for(int i=2; i<=n; i++) Log[i]=Log[i>>1]+1;
    for(int i=1; i<=n; i++)
        for(int j=0; j<=17; j++) fa[i][j]=i;
    for(int i=1; i<=m; i++) {
        l1=read(), r1=read(), l2=read(), r2=read();
        int t=Log[r1-l1+1];
        Union(l1, l2, t); Union(r1-(1<<t)+1, r2-(1<<t)+1, t);
    }
    int ans=0;
    for(int i=1; i<=n; i++) ans += find(i, 0)==i;
    printf("%lld\n", 9*Pow(10, ans-1)%P);
}

 

posted @ 2017-03-21 20:48  Candy?  阅读(387)  评论(0编辑  收藏  举报