bzoj4569 [Scoi2016]萌萌哒

29 篇文章 0 订阅
24 篇文章 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,后者第二位与第五位不同。问满足以上所有条件的数有多少个。

1≤n≤10^5,1≤m≤10^5,1≤li1,ri1,li2,ri2≤n;并且保证ri1-li1=ri2-li2。
答案可能很大,因此输出答案模10^9+7的结果即可。

Solution


我果然还是太弱了
显然可以先考虑区间长度为1的情况,那么就是用并查集合并两个位置。设连通块数量为t,那么答案就是9*10^t(除去前导零)
拓展一下长度大于1的情况,可以把区间像倍增一样分成log份。令id[i][j]表示区间[i,i+2^j],合并的时候递归一下合并当前区间的左半部分和右半部分就好了。区间的总数是nlogn的,那么最多被合并nlogn次

Code


#include <stdio.h>
#include <string.h>
#include <math.h>
#define rep(i,st,ed) for (int i=st;i<=ed;++i)

typedef long long LL;
const int MOD=1000000007;
const int N=100005;

int idd[N][25],fa[N*25],tot;
bool vis[N*25];

int get_father(int x) {
    if (fa[x]==x) return x;
    fa[x]=get_father(fa[x]);
    return fa[x];
}

bool merge(int x,int y,int k) {
    int fx=get_father(idd[x][k]),fy=get_father(idd[y][k]);
    if (fx==fy) return false;
    fa[fx]=fy;
    merge(x,y,k-1); merge(x+(1<<(k-1)),y+(1<<(k-1)),k-1);
    return true;
}

int main(void) {
    int n,m; scanf("%d%d",&n,&m);
    rep(i,1,n) for (int j=0;i+(1<<j)-1<=n;j++) idd[i][j]=++tot;
    rep(i,1,tot) fa[i]=i;
    rep(i,1,m) {
        int l1,r1,l2,r2; scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
        int lg=log2(r1-l1+1);
        merge(l1,l2,lg); merge(r1-(1<<lg)+1,r2-(1<<lg)+1,lg);
    }
    int cnt=0;
    rep(i,1,n) {
        if (vis[get_father(idd[i][0])]) continue;
        vis[get_father(idd[i][0])]=1;
        cnt++;
    }
    LL ans=9;
    rep(i,2,cnt) ans=ans*10LL%MOD;
    printf("%lld\n", ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值