BZOJ4569 [Scoi2016]萌萌哒

题意

F.A.Qs Home Discuss ProblemSet Status Ranklist Contest 入门OJ ModifyUser   autoint Logout 捐赠本站
Problem 4569. -- [Scoi2016]萌萌哒

4569: [Scoi2016]萌萌哒

Time Limit: 10 Sec   Memory Limit: 256 MB
Submit: 1619   Solved: 842
[ Submit][ Status][ Discuss]

Description

一个长度为n的大数,用S1S2S3...Sn表示,其中Si表示数的第i位,S1是数的最高位,告诉你一些限制条件,每个条
件表示为四个数,l1,r1,l2,r2,即两个长度相同的区间,表示子串Sl1Sl1+1Sl1+2...Sr1与Sl2Sl2+1Sl2+2...S
r2完全相同。比如n=6时,某限制条件l1=1,r1=3,l2=4,r2=6,那么123123,351351均满足条件,但是12012,13
1141不满足条件,前者数的长度不为6,后者第二位与第五位不同。问满足以上所有条件的数有多少个。

Input

第一行两个数n和m,分别表示大数的长度,以及限制条件的个数。接下来m行,对于第i行,有4个数li1,ri1,li2
,ri2,分别表示该限制条件对应的两个区间。
1≤n≤10^5,1≤m≤10^5,1≤li1,ri1,li2,ri2≤n;并且保证ri1-li1=ri2-li2。

Output

 一个数,表示满足所有条件且长度为n的大数的个数,答案可能很大,因此输出答案模10^9+7的结果即可。

Sample Input

4 2
1 2 3 4
3 3 3 3

Sample Output

90

HINT

Source

[ Submit][ Status][ Discuss]

HOME Back

分析

考试的时候只会暴力并查集。后来参照Mangoyang的题解。

观察发现,限制条件的本质是使得一些位置只能填同样的字符,我们不妨把限制在一起的位置看做点,在它们之间连一条边

那么统一联通块里面的位置就只能填同一字符了,所以设联通快数为 x ,那么答案就是 9 × 10 x 1 (第一位不能填 0 ,所以少一种选择)

于是就有一个暴力的做法,用并查集维护联通块,每次对于一组限制 l 1 , r 1 , l 2 , r 2 ,暴力将两个区间的对应点合并,复杂度 O ( n 2 l o g n )

考虑怎么优化并查集的合并,由于是区间问题,所以很容易就想到用线段树或者 s t 表来维护

每次把可以询问区间拆成 l o g 个区间,区间与区间之间进行连边,难点在于最后怎么将区间之间的合并转化到点上

由于题目只需要最终询问一次,不妨利用 l a z y t a g 的思想,对每一种长度的区间用一个并查集来维护,最后算答案的时候将合并信息下传

具体来讲,考虑 s t 表的做法:设 f a ( i , j ) 表示左端点为 i 的长度为 2 j 的区间所在集合的 r o o t 的左端点

那么下传信息的时候只需要 ( i , j 1 ) , ( i + 2 j 1 , j 1 ) 分别和 ( f a ( i , j ) , j 1 ) 合并即可,最后统计一下 ( i , 0 ) 的联通块数 ,总复杂度 O ( n l o g 2 n )


代码

#include<bits/stdc++.h>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
    rg T data=0,w=1;
    rg char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-') w=-1;
        ch=getchar();
    }
    while(isdigit(ch))
        data=data*10+ch-'0',ch=getchar();
    return data*w;
}
template<class T>il T read(rg T&x){
    return x=read<T>();
}
typedef long long ll;

co int N=1e5+1,mod=1e9+7;
int fa[N][21],n,m;
int ask(int x,int y){
    return x==fa[x][y]?x:fa[x][y]=ask(fa[x][y],y);
}
int main(){
//  freopen(".in","r",stdin),freopen(".out","w",stdout);
    read(n),read(m);
    for(int i=1;i<=n;++i)
        for(int j=0;j<=20;++j) fa[i][j]=i;
    for(int l1,r1,l2,r2;m--;){
        read(l1),read(r1),read(l2),read(r2);
        int ps1=l1,ps2=l2;
        for(int i=20;~i;--i) if(ps1+(1<<i)-1<=r1){
            int p=ask(ps1,i),q=ask(ps2,i);
            if(p!=q) fa[p][i]=q;
            ps1+=(1<<i),ps2+=(1<<i);
        }
    }
    for(int j=20;j;--j)
        for(int i=1;i+(1<<j)-1<=n;++i){
            int p=ask(i,j),q=ask(i,j-1);
            fa[q][j-1]=ask(p,j-1);
            p=ask(i,j),q=ask(i+(1<<j-1),j-1);
            fa[q][j-1]=ask(p+(1<<j-1),j-1);
        }
    int ans=1;
    for(int i=1;i<=n;++i) if(fa[i][0]==i)
        ans=(ll)ans*(ans==1?9:10)%mod;
    printf("%d\n",ans);
    return 0;
}

观察发现,限制条件的本质是使得一些位置只能填同样的字符,我们不妨把限制在一起的位置看做点,在它们之间连一条边

那么统一联通块里面的位置就只能填同一字符了,所以设联通快数为 x ,那么答案就是 9 × 10 x 1 (第一位不能填 0 ,所以少一种选择)

于是就有一个暴力的做法,用并查集维护联通块,每次对于一组限制 l 1 , r 1 , l 2 , r 2 ,暴力将两个区间的对应点合并,复杂度 O ( n 2 l o g n )

考虑怎么优化并查集的合并,由于是区间问题,所以很容易就想到用线段树或者 s t 表来维护

每次把可以询问区间拆成 l o g 个区间,区间与区间之间进行连边,难点在于最后怎么将区间之间的合并转化到点上

由于题目只需要最终询问一次,不妨利用 l a z y t a g 的思想,对每一种长度的区间用一个并查集来维护,最后算答案的时候将合并信息下传

具体来讲,考虑 s t 表的做法:设 f a ( i , j ) 表示左端点为 i 的长度为 2 j 的区间所在集合的 r o o t 的左端点

那么下传信息的时候只需要 ( i , j 1 ) , ( i + 2 j 1 , j 1 ) 分别和 ( f a ( i , j ) , j 1 ) 合并即可,最后统计一下 ( i , 0 ) 的联通块数 ,总复杂度 O ( n l o g 2 n )


猜你喜欢

转载自www.cnblogs.com/autoint/p/10475845.html
今日推荐