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\)位);那么我们接下来应该找这一个块的第\(i-\lfloor \frac{i}{64}\rfloor\cdot 64\)位,也就是第\(i%64\)位,我们考虑二进制竖式减法,对\(64\)取模相当于每次减去\(64\),由于\(64\)很特殊,我们每次竖式减法都不会影响\(i\)的低\(6\)位,然后要一直把\(i\)更高的位全部变成\(0\)(比如,二进制数\(100101000\),对\(64\)取模之后就变成了\(000101000\));所以对\(64\)取模就相当于取低\(64\)位,也就是\(i\)&\(63\)

注释2:

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

这样复杂度就被优化了

posted @ 2024-03-08 23:52  最爱丁珰  阅读(2)  评论(0编辑  收藏  举报