binary trick

binary trick

几乎没用的东西

学了 wordRAMmodel,不过感觉还是直接分块预处理的方法(比较)快,而且好打

先直接上代码

namespace binary_trick
{//almost in AC0
	typedef unsigned int u32;
	typedef unsigned long long ull;
#define p2(x) (1ull<<(ull)(x))
#define bget(x,p) (((x)&p2(p))>>p)
#define bgets(x,l,r) (((x)&all_1[r])>>(l))
	const u32 blen=16,hfblen=blen>>1;
	const u32 base=(1<<blen)-1,hfbase=(1<<(blen>>1))-1;
	const ull max_u32=p2(32)-1;
#define MAX_U32 max_u32
	u32 pcnt[base+5],rv[base+5],bcl[base+5],bct[base+5];
	ull all_1[65];
	inline u32 naive_rev(u32 x)
	{
		static bool b[8];
		irep(i,0,7) b[i]=bget(x,i);
		irep(i,0,3) swap(b[i],b[7-i]);
		u32 r=0;
		irep(i,0,7) if(b[i]) r|=p2(i);
		return r;
	}
	inline void init()
	{
		pcnt[0]=0,pcnt[1]=1;
		irep(i,2,base) pcnt[i]=pcnt[i>>1]+(i&1);
		
		irep(i,0,hfbase) rv[i]=naive_rev(i);
		irep(i,hfbase+1,base) rv[i]=(rv[i&hfbase]<<hfblen)|rv[i>>hfblen];
		irep(i,0,hfbase) rv[i]<<=8u;
		
		bcl[0]=16,bcl[1]=15;
		irep(i,2,base) bcl[i]=bcl[i>>1]-1;
		
		bct[0]=0,bct[1]=0;
		irep(i,2,base) bct[i]=i&1?0:(bct[i>>1]+1);
		
		all_1[0]=1;
		irep(i,1,63) all_1[i]=(all_1[i-1]<<1ull)|1ull;
	}
	inline u32 hmw(u32 x)
	{ return pcnt[x>>16]+pcnt[x&base]; }
	inline u32 u64_hmw(ull x)
	{ return pcnt[x>>48]+pcnt[(x>>32)&base]+pcnt[(x>>16)&base]+pcnt[x&base]; }
	inline u32 u32_rev(u32 x)
	{ return (rv[x&base]<<16u)|rv[x>>16u]; }
	inline ull u64_rev(ull x)
	{ return (ull)(u32_rev(x>>32ull)|((ull)(u32_rev(x&MAX_U32))<<32ull)); }
	inline void u32_print(u32 x,char c=' ')
	{ cerr<<(bitset<32>)x<<c; }
	inline void u64_print(ull x,char c=' ')
	{ cerr<<(bitset<64>)x<<c; }
	inline u32 u32_clz(u32 x)
	{ return x>base?bcl[x>>16]:(bcl[x&base]+16); }
	inline u32 u64_clz(ull x)
	{
		if(x>>48>0) return bcl[x>>48];
		if(x>>32>0) return 16u+bcl[(x>>32)&base];
		return 32u+u32_clz(x&MAX_U32);
	}
	inline u32 u32_ctz(u32 x)
	{ return x&base?bct[x&base]:(16u+bct[x>>16u]); }
	inline u32 u64_ctz(ull x)
	{
		if(x&base) return bct[x&base];
		if((x>>16ull)&base) return bct[(x>>16ull)&base]+16u;
		return u32_ctz(x>>32ull)+32u;
	}
	inline u32 lowbit(u32 x) { return 1u<<u32_ctz(x); }
	inline ull lowbit(ull x) { return p2(u64_ctz(x)); }
	inline u32 highbit(u32 x) { return 1u<<(31u-u32_clz(x)); }
	inline ull highbit(ull x) { return p2(63ull-u64_clz(x)); }
	inline bool add_in(ull x,ull y)
	{ return !bcl[x>>48ull]&&!bcl[y>>48ull]; }//判断加法进位
	class u128
	{
	public:
		ull high,low;
		u128()=default;
		u128(int x) { low=x>0?x:-x; }
		u128(u32 x) { low=x,high=0; }
		u128(ull x) { low=x,high=0; }
		u128(ull hb,ull lb) { low=lb,high=hb; }
		~u128()=default;
		inline void part_print() { cerr<<'<'<<high<<','<<low<<'>'; }
		inline u128 friend operator & (u128 a,u128 b) { return u128(a.high&b.high,a.low&b.low); }
		inline void friend operator &=(u128& a,u128 b) { a.low&=b.low,a.high&=b.high; }
		inline u128 friend operator | (u128 a,u128 b) { return u128(a.high|b.high,a.low|b.low); }
		inline void friend operator |=(u128& a,u128 b) { a.low|=b.low,a.high|=b.high; }
		inline u128 friend operator ^ (u128 a,u128 b) { return u128(a.high^b.high,a.low^b.low); }
		inline void friend operator ^=(u128& a,u128 b) { a.low^=b.low,a.high^=b.high; }
		inline bool friend operator ! (u128 a) { return !(a.low|a.high); }
		inline u128 friend operator ~ (u128 a) { return u128(~a.high,~a.low); }
		inline u128 friend operator <<(u128 a,u32 w)
		{
			if(!w) return a;
			if(w>127u) { return u128(0ull,0ull); }
			u128 c(0ull,0ull);
			if(w<64u)
			{
				c.high=(a.low>>(ull)(64u-w))|(a.high<<(ull)w);
				c.low=a.low<<(ull)w;
			}
			else c.low=0ull,c.high=(bgets(a.low,0,127u-w))<<(w-64ull);
			return c;
		}
		inline void operator <<=(u32 w)
		{
			if(!w) return;
			if(w>127u) { low=high=0ull; return; }
			if(w<64u) high=(low>>(ull)(64u-w))|(high<<(ull)w),low=low<<(ull)w;
			else high=(bgets(low,0,127u-w))<<(w-64ull),low=0ull;
		}
		inline u128 friend operator >> (u128 a,u32 w)
		{
			if(!w) return a;
			if(w>127u) return u128(0ull,0ull);
			u128 c(0ull,0ull);
			if(w<64u)
				c.high=a.high>>(ull)w,
				c.low=(a.low>>(ull)w)|((a.high&all_1[w])<<(64ull-w));
			else c.high=0ull,c.low=a.high>>(w-64u);
			return c;
		}
		inline void operator >>=(u32 w)
		{
			if(!w) return;
			if(w>127u) { high=low=0ull; return; }
			if(w<64u) low>>=(ull)w,low|=(high&all_1[w])<<(64ull-w),high>>=(ull)w;
			else low=high>>(w-64u),high=0ull;
		}
		
		inline bool friend operator ==(u128& a,u128& b)
		{ return a.low==b.low&&a.high==b.high; }
		inline bool friend operator < (u128& a,u128& b)
		{ return a.high<b.high?1:(a.high==b.high?a.low<b.low:0); }
		inline bool friend operator > (u128& a,u128& b)
		{ return a.high>b.high?1:(a.high==b.high?a.low>b.low:0); }
		inline bool friend operator <=(u128& a,u128& b)
		{ return a.high<b.high?1:(a.high==b.high?a.low<=b.low:0); }
		inline bool friend operator >=(u128& a,u128& b)
		{ return a.high>b.high?1:(a.high==b.high?a.low>=b.low:0); }
		inline bool friend operator !=(u128& a,u128& b)
		{ return !(a==b); }
		
		inline u128 friend operator + (u128 a,u128 b)
		{
			u128 c;
			if(!bcl[a.low>>48ull]&&!bcl[b.low>>48ull])
			{ c.low=a.low+b.low,c.high=a.high+b.high+1ull; }
			else { c.low=a.low+b.low,c.high=a.high+b.high; }
			return c;
		}
		inline void friend operator +=(u128& a,u128 b)
		{
			if(!bcl[a.low>>48ull]&&!bcl[b.low>>48ull])
			{ a.low=a.low+b.low,a.high=a.high+b.high+1ull; }
			else { a.low=a.low+b.low,a.high=a.high+b.high; }
		}
		inline u128 friend operator - (u128 a,u128 b)
		{
			u128 c;
			if(a.low<b.low) c.high=a.high-b.high-1,c.low=a.low+(compl b.low)+1;
			else c.high=a.high-b.high,c.low=a.low-b.low;
			return c;
		}
		inline void friend operator -=(u128& a,u128 b)
		{
			if(a.low<b.low) a.high=a.high-b.high-1,a.low=a.low+(compl b.low)+1;
			else a.high=a.high-b.high,a.low=a.low-b.low;
		}
		inline u128 friend operator * (u128 a,u128 b)
		{
			return u128(a.high*b.high,a.low*b.low);
		}//it's not suggested
		
		operator bool () const { return (bool)(high|low); }
		
		inline void friend u128_print(u128 a);
		inline u128 friend u128_rev(u128 x);
		inline u32 friend u128_clz(u128 x);
		inline u32 friend u128_ctz(u128 x);
		inline u32 friend u128_hmw(u128 x);
		inline u128 friend u128_lowbit(u128 x);
		inline u128 friend u128_highbit(u128 x);
	};
	const u128 unit_128(0ull,1ull);
	inline void u128_print(u128 a) { cerr<<(bitset<64>)a.high<<(bitset<64>)a.low; }
	inline u128 u128_rev(u128 x) { return u128(u64_rev(x.low),u64_rev(x.high)); }
	inline u32 u128_clz(u128 x) { return x.high?u64_clz(x.high):64u+u64_clz(x.low); }
	inline u32 u128_ctz(u128 x) { return x.low?u64_ctz(x.low):u64_ctz(x.high)+64u; }
	inline u32 u128_hmw(u128 x) { return u64_hmw(x.low)+u64_hmw(x.high); }
	inline u128 u128_lowbit(u128 x) { return unit_128<<u128_ctz(x); }
	inline u128 u128_highbit(u128 x){ return unit_128<<(127u-u128_clz(x)); }
	inline u128 turtle_mul(u128 a,u128 b)
	{
		u128 c(0ull,0ull);
		while(b)
		{
			if(b&unit_128) c+=a;
			a<<=1u,b>>=1u;
		} return c;
	} 
	inline u128 turtle2_mul(u128 a,u128 b)
	{
		u32 tmp;
		u128 c(0ull,0ull);
		while(b)
		{
			tmp=u128_ctz(b);
			a<<=tmp;
			c+=a;
			b>>=tmp+1;
			a<<=1u;
		} return c;
	} 
	inline u128 slow_mul(u128 A,u128 B)
	{
		static u32 a[4],b[4];
		static ull tmp;
		a[0]=A.low&MAX_U32,a[1]=A.low>>32ull,a[2]=A.high&MAX_U32,a[3]=A.high>>32ull;
		b[0]=B.low&MAX_U32,b[1]=B.low>>32ull,b[2]=B.high&MAX_U32,b[3]=B.high>>32ull;
		u128 c( (ull)a[1]*(ull)b[1] , (ull)a[0]*(ull)b[0] );
		tmp=1ull*a[0]*b[1];
			c.high+=(tmp>>32ull)+add_in(c.low,(tmp&MAX_U32)<<32ull);
			c.low+=(tmp&MAX_U32)<<32ull;
		tmp=1ull*a[1]*b[0];
			c.high+=(tmp>>32ull)+add_in(c.low,(tmp&MAX_U32)<<32ull);
			c.low+=(tmp&MAX_U32)<<32ull;
		tmp=1ull*a[2]*b[0]+1ull*a[0]*b[2];
			c.high+=tmp;
		tmp=1ull*a[2]*b[1]+1ull*a[1]*b[2];
			c.high+=(tmp&MAX_U32)<<32ull;
		tmp=1ull*a[3]*b[0]+1ull*a[0]*b[3];
			c.high+=(tmp&MAX_U32)<<32ull;
		return c;
	}//it's the suggested way to get a*b
} namespace efX_bt=binary_trick;

可以用 u128 了,但是乘法挺慢的,而且没有除法,所以不能输出,毕竟本来就是给你当 bitset 用的,不过手动循环展开的好像还是快一点,不过不知道这个和编译器版本有没有关系

预处理的时间不到 10ms,空间不到 1MB,应该没有什么会被卡的地方

命名的规范是 u32_,u64_,u128_ 表示对应的函数

rev=按位翻转,hmw=1的个数,highbit=最高位,lowbit=最低位,clz=前导0的数量,ctz=后缀0数量

完全没用的东西

要计算复杂度,我们需要假设我们做那些操作是 O(1) 的,这个就是我们采用的 model

一个 model 不能过于不现实,比如我们一般假设 ±O(1) 的运算,但在高精的情形下这就显然不合理

不合理的原因是没有考虑位长 w,也就是我们一个 unsigned int 或者 unsigned long long 能存储的二进制位数

考虑了之后,我们认为 bitand,bitor,xor,compl,±,×,<,>,=,&,*O(1) 的,之所以说是认为,是因为 × 其实不能做到 O(1),不过这个就涉及到比较底层的设计了,和算法关系不大,因为计算机算乘法也很快,所以就认为 × 也是 O(1) 的了

考虑到现在的 cpuw 一般取 3264

这个 model 就叫 word RAM model

还有一个常见的(一般是默认的)假设是 w=Ω(logn),要不然我们取地址都不是 O(1) 的,就感觉不太能做

这些操作被称为 AC0 基类,因为还可以扩展出一些其他的操作也是 O(1)

clz&ctz

这个东西直接做是不能做到 O(1) 的,最 naive 的就是按位枚举计数,这样是 O(w)

然后二分就可以做到 O(logw)

但如果我们预处理,那么就可以做到 O(1)clz(当然不是上面那种分段打表的做法)

我们从 (a1a2a3a4)(a2a3a40)(a2a3a41) 连边,然后跑欧拉回路,可以在 O(w) 的时间内制备出一张类似 hash 表的东西,问的时候就可以 O(1)log2x

因为我们假设了 w=Ω(logn) ,所以 O(w) 的制备时间认为白给的

然后 clz 都做了,对于 ctz,我们只需要 O(1) 求出 lowbit(x) 就可以了,写过树状数组就知道这是 xbitandx,不过我们一般认为 wordunsigned 的,所以还需要手动模拟一下补码的转化

popcount

这个又叫 Hamming Weight,也是我代码中的命名

直接枚举显然也是 O(w)

然后可以分治,以 b 为块长分块,每次合并使块长变为 2b,块内记录块内 1 的个数,一个块的值域是 2b,显然不会溢出

这样需要构造出形如 00010001 这样的数,即 2i10 后接一个 1,预处理是 O(logw) 的,但是发现分治过程就是这个反过来,所以其实不用预处理,可以在线制备,依然是 O(logw) 的(不过这么慢的写法不会有人用就是了)

考虑块的值域是 O(2b) 而最后答案不超过 O(w),所以试着在 b=O(logw) 时就停止分治

之后我们假设问题是在一个序列上,我们有 c0,c1,c2cO(logw) 表示每个块内的答案,我们要求 ci,最简单的做法就是卷积,而在一个 word 内卷积是 O(1) 的,这时我们发现最后一个块内的值就是答案

既然最后一次可以用卷积提速,那之前的操作也可以用卷积提速,把每次分治合并变为卷积合并,这样每次块长从 b 变为 2b1,只会迭代 O(α(w)) 次(α() 表示反阿克曼函数)

popcount 被证明了不能在 AC0O(1) 的(不考虑分段打表这种需要 O(2w/c) 的预处理时间的做法,这样的预处理时间在理论上不可接受)

pack&unpack

我们想把一堆分开的数放在一起(pack),或者进行相反的操作(unpack

pack 对于数的规模有要求,假设我们有 t 个块,每个块的末尾有 wt2 的位置存储了我们想要的数据,我们只需要构造出 P=0000001001(有 t11,相邻的 1 的距离为 (t1)wt21

原数与 P 卷积的最高位的块就是我们需要的答案

不难发现再卷一次就可以 unpack 了,不过需要忽略无关位,这个预处理出无关位是 0,有关位为 1 的数就可以了

posted @   嘉年华_efX  阅读(74)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示

目录导航