「BZOJ2393」Cirno 的完美算数教室
AFO 前的最后一篇题解,难蚌。
终于遇到了一个能写 TJ 的数论题了qwq,这个whk废物没救了。
题意
给定 l 和 r,求 l∼r 之间能整除一个仅由 2 和 9 组成的数的个数。 $1 \le l < r \le 10^{10} $(原题范围貌似写的不对)
解析
容斥原理的一个练习题,可以先用 dfs
O(2r) 求出所有 ≤r 的 baka 数加入集合 P ,然后 O(n2) 筛选掉每个对于 x,y∈P(x<y) 时 lcm(x,y)=y 的 y,并将筛选完的 x 加入 一个新集合 U。
(PS:lcm(a,b)=gcd(a,b)ab),筛选可以通过排序数组后把对应位置元素置 0 后续跳过的方案)
如何处理 lcm 相同导致重复筛选的情况呢?可以通过 dfs
搜索并剪枝的方式枚举子集并去重。
~(最开始本蒟蒻用了 map
判重不幸 TLE
了,这个复杂度是 O(n3),210 肯定过不了,警钟长鸣)~~
定义一个 calc(t1,t2,lcm)
函数,当达到边界条件t1≥∣U∣ 时根据 t2 的奇偶性来确定当前的 ans 是需要增加还是排斥。否则继续搜索。在继续搜索之前,将当前的 lcm 乘以 Ut1 并除以它们的最大公约数,这样可以计算出新的 lcm。如果新的 lcm≤r,则继续搜索,参数的 t2 需要加 1,表示当前的数字可以被 baka 数整除。
时间复杂度为 O(2nlogr)。(logr 来源于 gcd 的计算)
代码
不行本蒟蒻懒得打注释了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| #include <bits/stdc++.h> #define ll long long using namespace std; ll l,r,ans; vector<ll> nums,u; void dfs(ll num){ if(num>r) return; if(num) nums.push_back(num); dfs(num*10+2),dfs(num*10+9); } void calc(ll t1,ll t2,ll lcm){ if(t1>=u.size()){ if(t2&1) ans+=r/lcm-(l-1)/lcm; else if(t2) ans-=r/lcm-(l-1)/lcm; return; } calc(t1+1,t2,lcm); lcm=lcm*u[t1]/__gcd(lcm,u[t1]); if(lcm<=r) calc(t1+1,t2+1,lcm); } int main(){ cin>>l>>r; dfs(0),sort(nums.begin(),nums.end()); for(int i=0;i<nums.size();i++){ if(!nums[i]) continue; u.push_back(nums[i]); for(int j=i+1;j<nums.size();j++) if(nums[j]&&!(nums[j]%nums[i])) nums[j]=0; } calc(0,0,1),cout<<ans; return 0; }
|