CF710D Two Arithmetic Progressions 题解
CF710D Two Arithmetic Progressions
根号分治薄纱数论
看日报学习的根号分治:暴力美学——浅谈根号分治 - paulzrm 的博客。
开始想学ODT的映射思想的推广 - 金珂拉 的博客,结果先学了ODT
,又学了根号分治,才搞懂前置知识。
-
什么是根号分治
根号分治,是暴力美学的集大成体现。与其说是一种算法,我们不如称它为一个常用的 trick。
其实就是将两个极端的暴力算法融合,根据不同数据选择更优的暴力,即可保证复杂度始终在可接受范围内。
-
如何使用根号分治
即写出两个不同暴力算法(通常是在小范围内很快的和在大范围内很快的),之后确定一个阈值,在不同范围选择不同暴力。
大概根号分治就是这样,具体请看暴力美学——浅谈根号分治 - paulzrm 的博客。
在这道题中,我们需要思考两种方法(定 $t$ 为阈值,默认 $a_1\ge a_2$,默认 $l=\max(l,\max(b_1,b_2))$):
-
$a_1\le t$,则 $a_2\le t$。以 $\text{lcm}(a_1,a_2)$ 为一个循环节,显然的,一个循环节内至多有一个数同时出现在两个数列中。因此暴力枚举 $[\ l,\min(l+\text{lcm},r)\ ]$ 中的每一个数,若有 $i$ 使得 $i\in \text{数列1}\ \text{and}\ i \in \text{数列2}$,则可知共有 $\frac{(r-i)}{\text{lcm}+1}$ 个满足条件的值;反之没有所求值,输出
0
。 -
$a_1\gt t$。则可以暴力枚举 $\text{数列1}$ 中的每一个处于 $[\ l,r\ ]$ 的数,若它同时处于 $\text{数列2}$ 则
ans++
。
之后,我们进行复杂度分析:
- 对于暴力1:时间复杂度为 $O(\text{lcm}(a_1,a_2))$。
- 对于暴力2:时间复杂度为 $O(\frac{r-l+1}{a_1})$。
以下内容不保证正确性(数学太差了哭):
故期望复杂度 $O(\text{lcm}(a_1,a_2)+(\frac{r-l+1}{a_1}))$,即 $O((t\times a_2)+(\frac{2e9}{t}))$,对于 $a_2$ 有限制 $a_2\le t$,使 $a_2=t$,则可算出阈值 $t=\sqrt[3]{2e9}$。约 $1259$。
对于阈值的计算并不准确,如果有更优方法欢迎指正。
代码:
//pre
const int t=1259;
signed main() {
LL ans=0;
LL a1,b1,a2,b2,l,r;
read(a1,b1,a2,b2,l,r);
if(b1>l) l=b1;
if(b2>l) l=b2; //保证l的大小为 b_1,b_2,l 中的最大值
if(a2>a1) swap(a1,a2),swap(b1,b2); //保证 a_1>a_2
LL lcm=a2*a1/__gcd(a1,a2); //计算lcm (lcm(a,b)*gcd(a,b)=a*b)
if(a1<t) { //a_1 小于阈值
for(int i=l;i<=min(l+lcm,r);i++) {
if((i-b1)%a1==(i-b2)%a2&&(i-b2)%a2==0) { //若满足题意
ans=(r-i)/lcm+1; //剩下有几个循环节并加上当前位置的一个
break;
}
}
}
else {
LL x;
if(b1>=l) x=b1;
else x=b1+ceil(1.00*(l-b1)/a1)*a1;//确定从哪里开始枚举(起始点要属于数列1)
for(;x<=r;x+=a1) if((x-b2)%a2==0&&(x-b2)/a2>=0) ++ans; //满足题意则ans++
}
write(ans); //输出
return 0; //好习惯捏
}
pre
前的内容为头文件与快读快写,如果需要戳这里。