[SCOI2016]萌萌哒

4569: [Scoi2016]萌萌哒

Time Limit: 10 Sec  Memory Limit: 256 MB
Submit: 1554  Solved: 791
[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

Solution

妙题。

维护相等关系怎么办?

各种匹配都用不上。

感到无从下手。

我们就不考虑区间相等的问题了。

转化研究对象,

区间相等本质上是点的对应相等。

而一些点的位置都要相等的话,那么只能取同样的一个值。具体是哪个值没有关系。(当然不能前导零)

维护相等关系,用并查集。

所以,我们可以枚举所有的位置,每个位置开始是一个联通块。

再对每一个位置枚举所有条件,然后合并。答案就是9*10^(num-1) num为联通块的个数。

O(n^2)

这样合并太暴力了。

但是最后的查询只有O(n)

我们考虑均衡到O(nlogn)

一个浪费的地方在于,我们每个点要枚举所有的询问,其实这个询问是一个区间相等的关系。

能不能直接把区间相等直接用并查集并起来呢?

灵活的拆分区间,

可以比较自然地想到倍增。

所以,我们也可以把并查集利用倍增来进行实现。

把每个位置拆成logn个点。

第i个点,表示,x开始,连续2^i的子串。

两个条件相等,就二进制拆分成logn个区间,然后对应的并查集合并。

O(nlogn询问)

最后查询答案呢?

如果知道最后的每个点的相等关系就容易处理了。

我们可以拆分并查集。

2^N=2^(N-1)+2^(N-1)

从大到小枚举所有的nlogn个并查集点。

对于并查集的点k,把k的父亲找出来,自己和父亲代表的区间都拆成两半,前后部分分别再并起来。

复杂度O(nlogn)

其实,我们就是用倍增拆点的并查集维护了区间相等关系。

可以比较快地拆分区间,最后的并查集拆分复杂度也有保证。

其实感觉挺类似线段树的(虽然线段树不是二进制拆分)

都是把区间拆分。

用大的区间合并而不再往下分,就类似线段树的懒标记。

最后到叶子,就是懒标记下放。

update:2018.12.8

更具体地来考虑复杂度为什么变优秀了:

最后一共其实就是一棵并查集树,每个点只有一个father

但是期间每次枚举区间的O(n^2)的话,平均每个点要规定O(n)次father,其实最后就只需要一个。

这种倍增并查集的方式,本质上对这些关系进行了压缩。只有O(logn)个了。

通过合并冗余的关系,减少过程中的关系调整次数,就得到了优秀的复杂度。

(其实懒标记也是这样的)

代码:

#include<bits/stdc++.h>
#define reg register int
#define il inline
#define numb (ch^'0')
#define num(x,y) (y*n+x)
using namespace std;
typedef long long ll;
il void rd(int &x){
    char ch;x=0;bool fl=false;
    while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true);
    for(x=numb;isdigit(ch=getchar());x=x*10+numb);
    (fl==true)&&(x=-x);
}
namespace Miracle{
const int N=1e5+5;
const int mod=1e9+7;
int n,m;
int fa[N*20];
bool buc[N];
int fin(int x){
    return fa[x]==x?x:fa[x]=fin(fa[x]);    
}
void merge(int x,int y){
    int k1=fin(x),k2=fin(y);
    if(k1!=k2) fa[k1]=k2;
}
int main(){
    rd(n);rd(m);
    for(reg i=1;i<=n*20;++i) fa[i]=i;
    int l1,r1,l2,r2;
    for(reg i=1;i<=m;++i){
        rd(l1);rd(r1);rd(l2);rd(r2);
        int len=r1-l1+1;
        for(reg j=19;j>=0;--j){
            if(len>=(1<<j)){
                merge(num(l1,j),num(l2,j));
                l1+=(1<<j),l2+=(1<<j);
                len-=(1<<j);
            }
        }
    }
    for(reg j=19;j>=1;--j){
        for(reg i=1;i<=n;++i){
            if(i+(1<<j)-1>n) break;
            int to=fin(num(i,j));
            merge(num(i,(j-1)),to-n);
            merge(num(i+(1<<(j-1)),(j-1)),to-n+(1<<(j-1)));    
        }
    }
    ll ans=9;
    ll cnt=0;
    for(reg i=1;i<=n;++i){
        int k=fin(num(i,0));
        if(!buc[k]) buc[k]=1,++cnt;
    }
    for(reg i=1;i<=cnt-1;++i){
        ans=(ans*10)%mod;
    }
    printf("%lld",ans);
    return 0;
}

}
int main(){
    Miracle::main();
    return 0;
}

/*
   Author: *Miracle*
   Date: 2018/12/8 15:01:18
*/
原文地址:https://www.cnblogs.com/Miracevin/p/9873655.html