BZOJ 4569 [Scoi2016]萌萌哒

题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=4569

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的大数的个数,答案可能很大,因此输出答案模109+7的结果即可。

Sample Input

4 2
1 2 3 4
3 3 3 3

Sample Output

90


Solution

Cell: 这题看上去就是容斥
考虑没有任何限制时,因为不能有前导零,所以答案显然为10n-1*9。每添加一个限制,某些位的数字就捆绑在了一起,必须相同。则可以想到这样一个O(n2)暴力:把每一位看做一个节点,大力把l1~r1与l2~r2中对应的节点连起来,最后在每个联通块里的数字都必须是相同的。dfs一下记录联通块的个数为tot,则答案为10tot-1*9。

这个暴力怎么看都会超时的,因为每次连边太多太慢了。如果每次只要连很少的边,到最后再统计整理一下出答案就好了。貌似有个东西能很好的处理以上操作——ST表。

首先像ST表一样,建立出图0~logn。第i幅图的第j个节点表示大数中Sj~Sj+2i-1这个区间的数字。对于每个限制的区间,都可拆成长为2k的两个覆盖此区间的可重叠小区间。然后在第k幅图中把两个区间对应的小区间连起来。此处复杂度为O(n)。
图1

处理好那m个条件后,只要把第1~logn幅图的联通信息转移到第0幅图,然后就跟暴力一样了。转移的方法也很简单,在第i幅图中对于每个联通块,若点a与点b有连边,则只需要在第i-1幅图中把a的前半个区间与b的前半个区间连起来,再把a的后半个区间与b的后半个区间连起来即可。每幅图最多有n个节点,一共logn幅图,所以复杂度为O(nlogn)。
这里写图片描述


Code

这里写图片描述

#include <cstdio> 
#include <cstring> 
#include <cctype> 
#include <algorithm> 
using namespace std; 
#define N 100010 
#define Log 17 
#define mod 1000000007 
#define foru(i,l,r) for (int i=l; i<=r; i++) 
#define ford(i,r,l) for (int i=r; i>=l; i--) 
#define fore(i,p,v) for (int i=cur[p], v=edge[i].v; i; i=edge[i].pre, v=edge[i].v) 
#define read(x) (x=getint()) 
#define setmem(a,x) memset(a,x,sizeof(a)) 
typedef long long LL; 

struct Tedge {int v,pre;}; 
int n,m,ans,lg[N],bin[N],id[N],cnt,vst[N]; 
struct Tgraph 
{ 
    Tedge edge[N*5]; 
    int en,cur[N]; 
    void addedge(int u, int v); 
    void dfs(int p); 
} graph[Log]; 

int getint() 
{ 
    char ch; int x; 
    while (!isdigit(ch=getchar())); 
    x=ch-'0'; 
    while (isdigit(ch=getchar())) x=x*10+ch-'0'; 
    return x; 
} 
void Tgraph::addedge(int u, int v) 
{ 
    edge[++en]=(Tedge){v,cur[u]}, cur[u]=en; 
    edge[++en]=(Tedge){u,cur[v]}, cur[v]=en; 
} 
void Tgraph::dfs(int p) 
{ 
    id[++cnt]=p, vst[p]=1; 
    fore(i,p,v) 
        if (!vst[v]) 
            dfs(v); 
} 
LL pow(LL a, int b) 
{ 
    LL ans=1; 
    while (b) 
    { 
        if (b&1) ans=ans*a%mod; 
        b>>=1, a=a*a%mod; 
    } 
    return ans; 
} 
int main() 
{ 
    read(n), read(m); 
    foru(i,2,n) lg[i]=lg[i>>1]+1; 
    bin[0]=1; 
    foru(i,1,lg[n]) bin[i]=bin[i-1]<<1; 
    foru(i,1,m) 
    { 
        int l1=getint(), r1=getint(), l2=getint(), r2=getint(), j=lg[r1-l1+1]; 
        graph[j].addedge(l1,l2); 
        graph[j].addedge(r1-bin[j]+1,r2-bin[j]+1); 
    } 
    ford(i,lg[n],1)  
    { 
        setmem(vst,0); 
        for (int j=1; j+bin[i]-1<=n; j++) 
            if (!vst[j]) 
            { 
                cnt=0; 
                graph[i].dfs(j); 
                foru(k,1,cnt-1) 
                { 
                    graph[i-1].addedge(id[k],id[k+1]); 
                    graph[i-1].addedge(id[k]+bin[i-1],id[k+1]+bin[i-1]); 
                } 
            }        
    } 
    setmem(vst,0); 
    int tot=0; 
    foru(i,1,n) 
        if (!vst[i]) 
        { 
            tot++; 
            graph[0].dfs(i); 
        } 
    printf("%lld\n",pow(10,tot-1)*9%mod); 
    return 0; 
}

若判断连通块用并查集的话能更快
这里写图片描述

#include <cstdio>  
#include <cstring>  
#include <cctype>  
#include <algorithm>  
#include <vector> 
using namespace std;  
#define N 100010  
#define Log 17  
#define mod 1000000007  
#define foru(i,l,r) for (int i=l; i<=r; i++)  
#define ford(i,r,l) for (int i=r; i>=l; i--)  
#define read(x) (x=getint())  
#define setmem(a,x) memset(a,x,sizeof(a))  
typedef long long LL;  

int n,m,ans,lg[N],bin[N];  
struct Tgraph  
{  
    int fa[N];  
    int getfa(int x); 
    void uni(int a, int b); 
    void init(int id); 
} graph[Log];  
vector<int> id[N]; 

int getint()  
{  
    char ch; int x;  
    while (!isdigit(ch=getchar()));  
    x=ch-'0';  
    while (isdigit(ch=getchar())) x=x*10+ch-'0';  
    return x;  
}  
int Tgraph::getfa(int x) 
{ 
    return fa[x]==x? x: fa[x]=getfa(fa[x]); 
} 
void Tgraph::uni(int a, int b) 
{ 
    fa[getfa(a)]=getfa(b); 
} 
void Tgraph::init(int id) 
{ 
    for (int i=1; i+bin[id]-1<=n; i++) fa[i]=i; 
} 
LL pow(LL a, int b)  
{  
    LL ans=1;  
    while (b)  
    {  
        if (b&1) ans=ans*a%mod;  
        b>>=1, a=a*a%mod;  
    }  
    return ans;  
}  
int main()  
{   read(n), read(m);  
    foru(i,2,n) lg[i]=lg[i>>1]+1;  
    bin[0]=1;  
    foru(i,1,lg[n]) bin[i]=bin[i-1]<<1;  
    foru(i,0,lg[n]) graph[i].init(i); 
    foru(i,1,m)  
    {  
        int l1=getint(), r1=getint(), l2=getint(), r2=getint(), j=lg[r1-l1+1];  
        graph[j].uni(l1,l2);  
        graph[j].uni(r1-bin[j]+1,r2-bin[j]+1);  
    }  
    ford(i,lg[n],0)   
    {  
        for (int j=1; j+bin[i]-1<=n; j++) id[j].clear(); 
        for (int j=1; j+bin[i]-1<=n; j++) id[graph[i].getfa(j)].push_back(j); 
        for (int j=1; i&&j+bin[i]-1<=n; j++) 
            foru(k,0,(int)id[j].size()-2) 
            {  
                graph[i-1].uni(id[j][k],id[j][k+1]);  
                graph[i-1].uni(id[j][k]+bin[i-1],id[j][k+1]+bin[i-1]);  
            }              
    }  
    int tot=0;  
    foru(i,1,n)  
        if (id[i].size())  
            tot++;  
    printf("%lld\n",pow(10,tot-1)*9%mod);  
    return 0;  
}

把vector换成邻接表后又能快一点,代码就不放了
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值