【BZOJ 4569】【SCOI】【萌萌哒】【并查集】【倍增均摊优化】

2 篇文章 0 订阅
1 篇文章 0 订阅

这里写图片描述
好题配好图


萌萌哒

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


思路:

【1】30解法:

暴力枚举所有对应的点并用并查集合并,最后找出还有多少组可以自由分配的数目,即集合数;

【2】AC解法:

题目解决思路还是运用并查集,但是明显的,暴力去求解那么T掉便是十拿九稳的事情;

那么肿么办?本题配对方式是按照区间配对的,那么会不会有一种叫做区间并查集的东西?没有,但是可以将其转化一下;

做法大概是:

将一对区间A,B各自分为两个长度为log(len)(len为区间长度)的子区间,这两个区间会有重复,但是重复操作不会影响并查集;并将

对两队区间(A1,B1 );(A2,B2)的首位合并;
从长度为2^log(n)的长度开始枚举,然后枚举当前长度的以点A(1)~A(n-2^log +1)开头的所有区间然后将所有区间二分,等分为前后两段,分别将两子区间区间的首位与所属集合区间的首位进行合并 , 并记录在长度为2^log(i-1)的状态里


分析总结:
开始看题解时觉得是这个道理呢,但是不知道为什么在不经意之间这道题目就从O(n*m)转化成了O(n*log(n));
后来想了想,这道题运用了并查集的性质:
【1】无先后性:区间合并不管先后,只管你是否在这个区间里,
所以每次合并时其实是在将原来这个区间所属集合用区间开头表示出来(由于有长度限制后便很好表示),然后将长度为2^i时所属集合与2^(i-1)时所属集合合并起来

那么合并一个区间可以通过不断的二分使其变为log级别的深度,有由于每次总体数目为n,所以就变成了n*log(n)的级别
;总觉得和归并排序的时间复杂度算法很像


代码如下:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define M 3000001
using namespace std;
int n , m , cnt = 0;
int log[M] , tim[M];
const int mod = 1e9 + 7;
struct node{
    int f[M];
    void Init(){for(int i = 1 ; i <= n ; ++i)f[i] = i;}
    int find(int x){return f[x] == x ? x : f[x] = find(f[x]);}
    void solve (int x , int y){f[find(x)] = find(y);}
}st[20];
long long qp(int tp , int k){
    long long ans = 1 , t = tp;
    for(int i = k; i ; i >>= 1){
        if(i & 1)ans = t * ans % mod;
        t = t * t % mod;
    }
    return ans;
}
int main(){
    freopen("monokuma.in","r",stdin);
    freopen("monokuma.out","w",stdout);
    scanf("%d %d",&n,&m);
    for(int i = 0 ; i <  20 ; ++i)st[i].Init();
    for(int i = 2 ; i <= n  ; ++i)log[i] = log[i/2] + 1;
    for(int i = 1 ; i <= m ; ++i){
        int a , b , c , d , len;
        scanf("%d%d%d%d",&a,&b,&c,&d);
        len = b - a + 1;
        st[log[len]].solve(a , c);
        st[log[len]].solve(b - (1 << log[len]) + 1 , d - (1 << log[len]) + 1);
    }
    for(int i = log[n] ; i ; --i){
        for(int j = 1 ; j + (1 << i) <= n ; ++j){
            int x = st[i].find(j);
            st[i - 1].solve(j , x);
            st[i - 1].solve(j + (1 << (i - 1)) , x + (1 << (i - 1)));
        }
    }
    for(int i = 1 ; i <= n ; i++)if(st[0].f[i] == i)cnt++;
    long long anss = 9 * qp(10 , cnt - 1) % mod;
    printf("%I64d\n",anss);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值