[SCOI2010]幸运数字
做题时间:2022.8.6
\(【题目描述】\)
求区间 \([a,b](1\leq a\leq b\leq 10^{10})\) 所有由 \(8\) 或 \(6\) 组成的数字的倍数的个数。
\(【输入格式】\)
一行两个正整数表示 \(a,b\)
\(【输出格式】\)
一行一个整数表示答案
\(【考点】\)
容斥原理、DFS、剪枝
\(【做法】\)
首先 \(ans[a,b]=ans[1,b]-ans[1,a-1]\) ,可以去除下界限制。其次考虑 \(10^{10}\) 的以内由 \(8\) 和 \(6\) 组成的幸运数字,发现一共有 \(2046\) 个。接下来考虑它们的倍数,由于可能一个整数同时是多个幸运数字的倍数,贡献会重复计算,考虑容斥。问题相当于 至少为1个幸运数字的倍数的数的个数 ,等价于一个交集,通过容斥原理基本柿子可知其等价于 同时为 \(1\) 个幸运数字的倍数的数的个数 \(-\) 同时为 \(2\) 个幸运数字的倍数的数的个数 \(+\) ...
同时为 \(k\) 个幸运数字的倍数的数的个数也就是 \(\lfloor b/\operatorname{lcm(a_1,a_2,...,a_k)}\rfloor\) ,其中 \(a_i\) 为幸运数字。
但是如果直接枚举所有 \(k\) 及 \(a_i\) ,复杂度为 \(\Theta(2^{2064})\) ,吃不消。考虑剪枝:
- 先若一个幸运数字是另一个的倍数,那么这个幸运数字就没有枚举的必要,去除掉这些数字后,复杂度降至 \(\Theta(2^{943})\)
- 若当前 \(lcm>b\) 那么对答案的贡献为0 ,此时后面的数就不需要枚举了。这使得复杂度大大下降
- 将幸运数字从大到小排序,使得 \(lcm\) 更快地 \(>b\)
\(【代码】\)
#include<cstdio>
#include<iomanip>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=1e5+50;
ll a,b;
ll num[N],ans[3];
int ed,pos=1;
void dfs(int k,int n,ll now)
{
if(k==n+1){
bool q=true;
for(int i=1;i<=ed;i++){//剪枝1
if(now%num[i]==0) q=false;
}
if(q) num[++ed]=now;
return ;
}
dfs(k+1,n,now*10+6);
dfs(k+1,n,now*10+8);
}
int Bit(ll b)
{
int cnt=0;
while(b){
cnt++;
b/=10;
}
return cnt;
}
bool cmp(ll a,ll b){return a>b;}
ll gcd(ll n,ll m){return m==0?n:gcd(m,n%m);}
void DFS(int k,int n,ll lcm,int cnt_num,ll Up)
{
if(k==n+1){
if(cnt_num) ans[pos]+=(cnt_num%2 ? 1 : (-1)) * (Up/lcm);
return ;
}
DFS(k+1,n,lcm,cnt_num,Up);
ll nlcm=lcm/gcd(lcm,num[k])*num[k];
if(nlcm<=0) return ;//注意可能爆long long
if(nlcm<=Up) DFS(k+1,n,nlcm,cnt_num+1,Up);//剪枝2
}
int main()
{
scanf("%lld%lld",&a,&b);
//算出与b同位的幸运数字个数
int c=Bit(b);
for(ll i=1;i<=c;i++) dfs(1,i,0);
//算ans[1,b]
while(num[ed]>b&&ed>1) ed--;
sort(num+1,num+1+ed,cmp);//剪枝3
DFS(1,ed,1,0,b);
sort(num+1,num+1+ed);
pos++;
//算ans[1,a-1]
while(num[ed]>a-1&&ed>1) ed--;
sort(num+1,num+1+ed,cmp);//剪枝3
DFS(1,ed,1,0,a-1);
printf("%lld\n",ans[1]-ans[2]);
return 0;
}

浙公网安备 33010602011771号