Unknown Mother-Goose

遇到这种题目就可以考虑bitset

下面介绍手写bitset,以神鱼的代码为例

#include<cstdio>
#include<iostream>
#include<cstring>
#define N 1000000003
#define reg register
using namespace std;

unsigned long long bs[(N>>6)+10];
unsigned long long tmp[65];
int n,m,s,l,ans;

inline int count(unsigned long long x){
    reg int res = 0;
    while(x){
        res += x&1;
        x >>= 1;
    }
    return res;
}

int main(){
    scanf("%d%d",&n,&s);
    m = (n>>6)+1;
    //每一个ull有八个字节
	//每个字节有八个bit
	//相当于一个ull可以有64个二进制位
	//所以我们用m个ull来模拟有n个二进制位的bitset 
    while(s--){
        scanf("%d",&l);
        if(l<64){ //两种情况判一下,保证加入的复杂度是 n/w
            memset(tmp,0,sizeof(tmp));
            for(reg int i=0;i<(l<<6);i+=l)
                tmp[i>>6] |= 1ull<<(i&63); //加入的数比较小,开另一个数组,作为重复单元
                //注释2 
            for(reg int i=0;i<=m;i+=l)
            for(reg int j=0;j<l;++j)
                bs[i+j] |= tmp[j];    
        }else{
            for(reg int i=0;i<=n;i+=l)
                bs[i>>6] |= 1ull<<(i&63);//注释1 
        }
    }
    --m;
    if((n&63)!=63) bs[m] &= (1ull<<(n+1-(m<<6)))-1; //特判一下最后一块
    bs[0] &= -2ull; //除去第 0 项
    for(reg int i=0;i<=m;++i) ans += count(bs[i]&(bs[i]<<1)&(bs[i]<<2));//考虑每一块的连续的三个一 
    for(reg int i=1;i<=m;++i) ans += count(bs[i]&(bs[i-1]>>62)&((bs[i-1]>>63)|(bs[i]<<1)));//考虑两块相接处的三个一 
    printf("%d",ans);
    return 0;
}

注释1:

首先i>>6是找到i所在的块(注意每64个位为一个块,最低位为第0位而不是第1位);那么我们接下来应该找这一个块的第ii6464位,也就是第i位,我们考虑二进制竖式减法,对64取模相当于每次减去64,由于64很特殊,我们每次竖式减法都不会影响i的低6位,然后要一直把i更高的位全部变成0(比如,二进制数100101000,对64取模之后就变成了000101000);所以对64取模就相当于取低64位,也就是i&63

注释2:

tmp数组相当于是模拟了bs数组的低64块(注意是不是位);由于64|l64,所以在bs数组的第64块(块的编号从0开始)一定是从这一块的第0位开始填写,然后接下来就是复制tmp的操作,如下图

这样复杂度就被优化了

posted @   最爱丁珰  阅读(12)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示