Codeforces 710D - Two Arithmetic Progressions (数论、暴力枚举)
710D - Two Arithmetic Progressions
题意
给定\(a_1,a_2,b_1,b_2,L,R\)
要求找出在\([L,R]\)中的\(x\)的数量,使得\(x\)满足
\(x=a_1k_1+b_1=a_2k_2+b_2\ (k_1,k_2\geq 0)\)
限制
Time limit per test: 1 second
Memory limit per test: 256 megabytes
\(0\lt a_1,a_2\leq 2\cdot 10^9\)
\(-2\cdot 10^9 \leq b_1,b_2,L,R \leq 2\cdot 10^9,\ L\leq R\)
思路
标程使用的是扩展欧几里得的解法,这里放一种奇妙的暴力求解法
由于\(a_1,a_2\gt 0\),所以随着\(k_1,k_2\)的增大,\(a_1k_1+b_1\)与\(a_2k_2+b_2\)也一定会增大
所以我们首先需要找出在满足\(k_1,k_2\geq 0\)的条件下\(a_1k_1+b_1\)与\(a_2k_2+b_2\)在\([L,R]\)范围内的左右边界
这里可能有点难理解,举个例子
如果\(a=7,b=5,L=10,R=30\)
也就是查找\(7k+5\)在\(k\geq 0\)的条件下在\([10,30]\)范围内的左右边界
可得,当\(k=1\)时得到左边界\(12\),在\(k=3\)时得到右边界\(26\)
所以将\(12\)与\(26\)看作\(7k+5\)在\([10,30]\)范围内的左右边界
将两个式子的左右边界求出,令其为\(X_1,Y_1\)与\(X_2,Y_2\)
(这里可以根据差值来进行计算)
X1=b1+max(0LL,(L-b1+a1-1)/a1*a1);
X2=b2+max(0LL,(L-b2+a2-1)/a2*a2); //向上取整求出左边界
Y1=X1+(R-X1)/a1*a1;
Y2=X2+(R-X2)/a2*a2; //向下取整求出右边界
假设我们找到了最小的满足条件的\(x\),使得\(x=a_1k_1+b_1=a_2k_2+b_2\ (k_1,k_2\geq 0)\)
根据周期性可以得到,下一个满足条件的\(x'\)一定满足\(x'=x+lcm(a_1,a_2)\)
所以根据周期个数,最终答案可以直接由\(\frac {\min(Y_1,Y_2)-x} {lcm(a_1,a_2)}+1\)得到(\(+1\)表示初始的\(x\))
那么问题就只剩一个了:求解最小的满足条件的\(x\)
由于我们已经得到了两个式子的左边界\(X_1,X_2\)
所以可以反复令其加上对应的\(a\)去枚举查找(但有很多种情况可以让其TLE)
while(X1^X2)
{
if(X1<X2)
X1+=a1;
else
X2+=a2;
}
所以在这部分的枚举之上进行优化
优化一:
当\(a_1,a_2\)较小,\(L,R\)范围较大,\(b_1,b_2\)差值较大时
例如(Test #27):
\(a_1=1\ b_1=-2000000000\ a_2=2\ b_2=2000000000\ L=-2000000000\ R=2000000000\)
对于这种情况,我们在枚举查找过程中肯定是反复对较小的那个左边界进行操作
所以可以利用向上取整的特点,一次性将缺少的部分向上取整来补上,做到“反复”
while(X1^X2)
{
if(X1<X2)
X1+=(X2-X1+a1-1)/a1*a1;
else
X2+=(X1-X2+a2-1)/a2*a2; //将较小的左边界根据差值向上取整做除法
}
优化二:
当\(\gcd(a_1,a_2)>1\),且\(b_1,b_2\)的值又保证两式子无交点(且值都较小时)时
例如(Test #28):
\(a_1=2\ b_1=0\ a_2=2\ b_2=1\ L=0\ R=1000000000\)
直接判断可能过于繁琐
不过我们可以对“反复枚举”的次数进行限制
如果在一定的次数之内“反复枚举”仍然无法找出最小的满足条件的\(x\),直接退出循环
由于之前发现交点的周期即为\(lcm(a_1,b_1)\),那么我们可以取\(\frac {lcm}{a_1}+\frac {lcm}{a_2}\)作为循环次数
ll tim=lcm/a1+lcm/a2; //限制循环次数
while(X1^X2&&tim--)
{
if(X1<X2)
X1+=(X2-X1+a1-1)/a1*a1;
else
X2+=(X1-X2+a2-1)/a2*a2; //将较小的左边界根据差值向上取整做除法
}
优化三:
当\(a_1,a_2\)为大素数时
例如(Test #68):
\(a_1=915583842\ b_1=-15\ a_2=991339476\ b_2=-12\ L=-15\ R=-5\)
在这种情况下,上述限制枚举次数的方式也将无济于事
但是考虑到一个特点,就是在这种大数情况下“反复枚举”将很快超出给定的\([L,R]\)范围
所以可以在循环内再加入一个条件来在保证可行性的情况下进一步限制枚举次数
ll tim=lcm/a1+lcm/a2; //限制循环次数
while(X1^X2&&tim--&&X1<=Y1&&X2<=Y2)
{
if(X1<X2)
X1+=(X2-X1+a1-1)/a1*a1;
else
X2+=(X1-X2+a2-1)/a2*a2; //将较小的左边界根据差值向上取整做除法
}
至此,便可以结束本题了,答案计算方式如上述\(\frac {\min(Y_1,Y_2)-x} {lcm(a_1,a_2)}+1\)
代码
(31ms/1000ms)
/*
* Author : StelaYuri
* Language : GNU G++ 14
*/
//#pragma comment(linker,"/STACK:1024000000,1024000000")
#pragma GCC optimize(3)
#include<bits/stdc++.h>
//#include<unordered_map>
//#include<ext/pb_ds/assoc_container.hpp>
//#include<ext/pb_ds/hash_policy.hpp>
#define closeSync ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define multiCase int T;cin>>T;for(int t=1;t<=T;t++)
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i<(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define perr(i,a,b) for(int i=(a);i>(b);i--)
#define pb push_back
#define eb emplace_back
#define mst(a,b) memset(a,b,sizeof(a))
using namespace std;
//using namespace __gnu_pbds;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> P;
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;
const double eps=1e-12;
const double PI=acos(-1.0);
const double angcst=PI/180.0;
const ll mod=998244353;
ll max_3(ll a,ll b,ll c){if(a>b&&a>c)return a;if(b>c)return b;return c;}
ll min_3(ll a,ll b,ll c){if(a<b&&a<c)return a;if(b<c)return b;return c;}
ll gcd(ll a,ll b){return b==0?a:gcd(b,a%b);}
ll qmul(ll a,ll b){ll r=0;while(b){if(b&1)r=(r+a)%mod;b>>=1;a=(a+a)%mod;}return r;}
ll qpow(ll a,ll n){ll r=1;while(n){if(n&1)r=(r*a)%mod;n>>=1;a=(a*a)%mod;}return r;}
ll qpow(ll a,ll n,ll p){ll r=1;while(n){if(n&1)r=(r*a)%p;n>>=1;a=(a*a)%p;}return r;}
void solve()
{
ll a1,a2,b1,b2,L,R,X1,X2,Y1,Y2;
cin>>a1>>b1>>a2>>b2>>L>>R;
X1=b1+max(0LL,(L-b1+a1-1)/a1*a1);
X2=b2+max(0LL,(L-b2+a2-1)/a2*a2); //向上取整求出左边界
if(X1>R||X2>R)
{
cout<<0<<'\n';
return;
}
Y1=X1+(R-X1)/a1*a1;
Y2=X2+(R-X2)/a2*a2; //向下取整求出右边界
ll g=gcd(a1,a2);
ll lcm=a1/g*a2;
ll tim=lcm/a1+lcm/a2; //限制循环次数
while(X1^X2&&tim--&&X1<=Y1&&X2<=Y2) //一旦找到最小x、次数耗尽、超出范围,则直接退出
{
if(X1<X2)
X1+=(X2-X1+a1-1)/a1*a1;
else
X2+=(X1-X2+a2-1)/a2*a2; //将较小的左边界根据差值向上取整做除法
}
if(X1^X2||X1>Y1||X2>Y2)
cout<<0<<'\n';
else
cout<<(min(Y1,Y2)-X1)/lcm+1<<'\n'; //根据较小的差值除以lcm再+1得出答案
}
int main()
{
closeSync;
//multiCase
{
solve();
}
return 0;
}