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的操作,如下图
这样复杂度就被优化了