CF215E Periodical Numbers

Link

我们枚举数的长度 \(len\),循环节长度 \(k\) ,算出范围内有多少个数的二进制是长度为 \(len\) 并且循环节长度为 \(k\) 的,记为 \(Ans_{len,k}\) ,这个可以用数位dp快速求,做法和 SP10649 Mirror Number 类似,就是在数位dp的过程中开一个辅助数组来记录当前状态对应的循环节是什么。

求出了 Ans 数组后,发现直接把 Ans 数组里所有的数的和加起来会出现算重的情况,比如长度为 8,循环节长度为 4 的情况中一定包含了长度为 8,循环节长度为 2 的情况,那么把他们减去就行了。也就是

\[Ans_{len,k} \leftarrow Ans_{len,k}-\sum _{g<k,g|k} Ans_{len,g} \]

注意删的顺序,要从小到大删,也就是保证删 k 的时候 k 的因子已经不存在算重了。

\(\texttt{Code:}\)

#include <bits/stdc++.h>
#define reg register
#define int long long
#define mem(x,y) memset(x,y,sizeof x)
#define ln puts("")
using namespace std;
template <class t> inline void read(t &s){
s=0;reg int f=1;reg char c=getchar();while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c))s=(s<<3)+(s<<1)+(c^48),c=getchar();s*=f;return;}
template<class t,class ...A> inline void read(t &x,A &...a){read(x);read(a...);return;}
template <class t> inline void write(t x){if(x<0)putchar('-'),x=-x;int buf[21],top=0;
while(x)buf[++top]=x%10,x/=10;if(!top)buf[++top]=0;while(top)putchar(buf[top--]^'0');
return;}
int a[70],t[70],f[70][70],Len,D;// Len:枚举的数在二进制下的长度, D:枚举的循环节长度,t辅助数组
int Ans[70][70];
inline int dfs(int dep,int zero,bool lim)
    //dep:当前枚举到的位数,zero表示从哪一位开始的算的,用来弄前导0之类的
{
	if(!dep)
		return zero==Len;	// 长度是否跟枚举的长度一样
	if(!lim&&~f[dep][zero])
		return f[dep][zero];
	reg int maxi=lim?a[dep]:1,res=0;
	for(int i=0;i<=maxi;++i)
	{
		reg int nzero=zero;
		if(i&&!nzero)
			nzero=dep;		// 当前位置开始算,之前都是前导零
		if(nzero-dep<D)		// 还没有填满 D 个数
		{
			t[nzero-dep]=i;
			res+=dfs(dep-1,nzero,lim&&(i==maxi));
		}
		else if(t[(nzero-dep)%D]==i)	// 看是否能跟循环节一样
			res+=dfs(dep-1,nzero,lim&&(i==maxi));
	}
	if(!lim)
		f[dep][zero]=res;
	return res;
}
inline int calc(int x,int l,int d)
{
	reg int cnt=0;
	while(x)
		a[++cnt]=(x&1),x>>=1;
	mem(f,-1);
	Len=l;
	D=d;
	return dfs(cnt,0,true);
}
signed main(void)
{
	reg int l,r;read(l,r);
	for(int i=1;i<=64;++i)
		for(int j=1;j<i;++j)
			if(!(i%j))
				Ans[i][j]=calc(r,i,j)-calc(l-1,i,j);
	for(int i=1;i<=64;++i)		// 去重
		for(int j=1;j<i;++j)
			if(!(i%j))
				for(int k=1;k<j;++k)
					if(!(j%k))
						Ans[i][j]-=Ans[i][k];
	reg int ans=0;
	for(int i=1;i<=64;++i)
		for(int j=1;j<i;++j)
			ans+=Ans[i][j];
	write(ans),ln;
	return 0;
}
posted @ 2020-09-03 14:37  StarlightTobor  阅读(156)  评论(0)    收藏  举报