【BZOJ-4569】萌萌哒 ST表 + 并查集

4569: [Scoi2016]萌萌哒

Time Limit: 10 Sec  Memory Limit: 256 MB
Submit: 459  Solved: 209
[Submit][Status][Discuss]

Description

一个长度为n的大数,用S1S2S3...Sn表示,其中Si表示数的第i位,S1是数的最高位,告诉你一些限制条件,每个条件表示为四个数,l1,r1,l2,r2,即两个长度相同的区间,表示子串Sl1Sl1+1Sl1+2...Sr1与Sl2Sl2+1Sl2+2...Sr2完全相同。比如n=6时,某限制条件l1=1,r1=3,l2=4,r2=6,那么123123,351351均满足条件,但是12012,131141不满足条件,前者数的长度不为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

Solution

这道题非常巧妙

先思考暴力,对每一位维护一个并查集,每次限制就是把那两个区间信息合并,最后答案根据剩有的计算即可

那么问题在于如何快速的合并

考虑线段树,分出来的区间过多,合并还是有问题,所以换种方法

倍增!建立ST表,相当于对同层的建出一棵类似树的东西,每层维护并查集,相当于把信息拆成$2^{?}$的两段

合并优先合并大的,这样一共是$nlogn$段,最多合并$nlogn$次

递归合并,发现合并过就可以跳出了,总的答案统计一下用快速幂计算一下即可

特判N=1

总的时间复杂度$O(nlogn*a(n))$

启发:倍增并不仅仅应用于LCA或SA求LCP之类的,应该灵活运用这种思想

Code

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
void Freopen() {freopen("game.in","r",stdin); freopen("game.out","w",stdout);}
#define P 1000000007
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while (ch<'0' || ch>'9') {if (ch=='-') f=-1; ch=getchar();}
    while (ch>='0' && ch<='9') {x=x*10+ch-'0'; ch=getchar();}
    return x*f;
}
int N,M;
long long Quick_Pow(long long x,long long y)
{
    long long re=1;
    for (int i=y; i; i>>=1,x=x*x%P)
        if (i&1) re=re*x%P;
    return re;
}
#define MAXN 100010
int fa[MAXN][21];
int Find(int x,int y) {if (fa[x][y]==x) return x; else return fa[x][y]=Find(fa[x][y],y);}
void Merge(int x1,int x2,int k)
{
    int f1=Find(x1,k),f2=Find(x2,k);
    if (f1==f2) return; 
    fa[f1][k]=f2;
    if (k--) {Merge(x1,x2,k),Merge(x1+(1<<k),x2+(1<<k),k);}
}
int Len;
int main()
{
    Freopen();
    N=read(),M=read();
    for (int i=1; i<=N; i++)
        for (int j=0; j<=17; j++)
            fa[i][j]=i;
    while (M--)
        {
            int l1=read(),r1=read(),l2=read(),r2=read();
            int k=0; while ((1<<k)<=r1-l1+1) k++; k--;
            Merge(l1,l2,k); Merge(r1-(1<<k)+1,r2-(1<<k)+1,k);
        }
    for (int i=1; i<=N; i++) if (fa[i][0]==i) Len++;
    printf("%d
",N==1? 10:int(9LL*Quick_Pow(10,Len-1)%P));
    return 0;
} 
原文地址:https://www.cnblogs.com/DaD3zZ-Beyonder/p/5734233.html