题目大意
小z热衷于数学。
今天数学课的内容是解不等式:(L le S*x le R) 。小z心想这也太简单了,不禁陷入了深深的思考:假如已知 (L,S,R,M),满足 (L le (S*x) mod M le R) 的最小正整数该怎么求呢?
输入格式
第一行包含一个整数 T ,表示数据组数T,接下来是 T行,每行为四个正整数M,S,L,R 。
输出格式
对于每组数据,输出满足要求的x值,若不存在,输出-1。
样例
样例输入
1
5 4 2 3
样例输出
2
算法分析:
- 通过暴力枚举答案我们可以得到部分分30
- 剩下的20分部分分可以用exgcd得到
- 现在考虑正解 首先对于这样一个式子(L le (S*x) mod M le R) 如果说不用mod M 就已经满足(L le (S*x) le R) 那么我们就可以直接求解x了
即(frac{L}{S} le x le frac{R}{S}) 如果x在这个区间中有整数解 那么整数解最小的就是我们的答案 直接求就好了 利用C++向下取整可以很方便的判断除法之后是不是是整数 即除完再乘 如果不等于原数显然就不是整数 我们这里让他向上取整 因为如果(frac{L}{S}) = 1.5的话 显然x最小取到2 然后再乘上我们的S 如果比R小说明合法可以用这个解 - 然后如果不满足上面这个式子我们就要进行递归求解了 因为对于这样一个式子(L le (S*x) mod M le R)
我们可以把mod M 写成(-M*y)的形式 那么我们的式子就转化成了(L le S*x - M*y le R) 然后以y为主元 移项可得
(-R + S*x le M*y le -L + S*x)
由这个式子我们知道 求x的最小值等价于求y的最小值 因为x变大 y也变大 y变大 x也变大
然后我们把它还原为取模的形式 得
(-Rmod Sle (M*y) mod S le -L mod S)
至于为什么可以直接取模 因为对于任何x满足 S*x 在L到R区间内 我们会在第一种情况中处理掉 那么此时剩下的x 我们可以理解为 (S*k le L le R le S*(k+1))
那么这时L 与 R都在同一个S的区间内 因此mod S 可以近似的理解为向左平移S的单位到不能再移动为止 所以这个式子仍然满足 - 而仔细观察这个取模后的式子 是不是与我们原始式子有那么亿丢丢像 所以我们可以重新定义一些数学符号 我们令:
L' = (-R mod S)
R' = (-L mod S)
S' = (M mod S)
M' = (S)
那么我们的问题就变成了一个在缩小的区间问题啊 - 这不就变成了递归了嘛
首先由上面我们推出的式子 再移项 可以知道: (M*y + L le S*x le M*y + R)
那么在区间尽可能小的情况下 我们可以把这整个区间看成一个点 那么此时就有:
x = (frac{L+M*y}{S}) = (frac{R+M*y}{S})
若y存在 则x也存在 - 关于上述递归可行性的证明:
原区间为L 到 R 区间长度为R-L+1 新区间长度:
(R' - L' + 1) = ((-R \% S) - (-L \% S)) + 1 = ((R - L)%S) + 1 (le) (R-L+1) - 因此我们的区间是一点点变小的 也就是可以用递归求解的 不断将一个大的问题转化为几个小的子问题不就是我们递归的一般思想嘛
- 所以大概思路已经出来了 不断判断是否有可行解 是否可以进一步递归求解 进一步递归求解之后是否可行 ……等等
Code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll dfs(ll m,ll s,ll L,ll R){
if(L > R || m < L)return -1;
s %= m;
int now = (L - 1)/s + 1;
if(now * s <= R)return now;
int l = (-R % s + s) % s,r = (-L % s + s) % s;
int y = dfs(s,m,l,r);
if(y == -1)return -1;
int x = (R + m*y)/s;
if(L <= s * x - m * y)return x;
return -1;
}
int main(){
int T;scanf("%d",&T);
while(T--){
ll m,s,l,r;scanf("%lld%lld%lld%lld",&m,&s,&l,&r);
printf("%lld
",dfs(m,s,l,min(r,m-1)));
}
return 0;
}