原题链接:E - Simple Math 3
题目大意
给定(A,B,C,D),求满足如下条件的正整数(i)的个数:对于任意的正整数(xin[A + B*i,A + C*i])都有(D mid x).
数据范围:
(1 leq T leq 10^4)组数据
(1 leq A < D)
(0 leq B < C < D)
(2 leq D leq 10^8)
思路
多组测试数据,单组复杂度应该是(log(D))或者根号的.方向可能就是求得形如下取整的和式再根号的数论分块.
根据鸽巢原理:对于(i)形成的区间长度不能达到(D)及以上,因为一个含有(D)个数的区间的序列所有数关于(D)的余数一共有(D)种,必然存在一个数使得被(D)整除.所以可以求出(i leq dfrac{D - 2}{C - B}).此后记(k = dfrac{D - 2}{C - B}).那么(k)就代表所有可能是答案的区间的个数,接下来需要从中挖掉不符合条件的区间的个数.
考虑一个区间是否存在(D)的倍数的条件,由于原来的形式太丑陋了,这里简写成考虑([l,r])区间不存在(D)的倍数的条件.一个比较符合直觉的想法是:(D)的倍数一定在区间之内,不过我们首先不看极端情况(倍数在区间边缘上),看一下一般的情况,这个时候假设有一个倍数存在于区间内,并且不在端点上,我们不妨把整个序列按(D)的倍数在的位置划分段落,再来看([l,r])会发现两个端点一定不属于同一个段落集合,那么反过来只要在一个段落集合里,那么就说明落在了一段以(D)倍数划分的段落以内,这个段落以内是不存在(D)的倍数的,所以只需要仿照分块时找下标的方式写成(leftlfloor dfrac{l}{D} ight floor == leftlfloor dfrac{r}{D} ight floor)等价于内部没有(D)的倍数.
不过这个等式是错的,因为还少了一种情况:当左端点(l)恰好是(D)的倍数时,同样会满足条件,但是显然这个时候区间是包含了(D)的倍数的,这里有个很神棍的解决办法,把左端点往左移动一位,条件换成(leftlfloor dfrac{l - 1}{D} ight floor == leftlfloor dfrac{r}{D} ight floor).正确性在于一开始就保证了所有的区间长度在(D-1)之内,而往左过去一位不会使不相同的变得相同,只会让(l)是左端点的情况被删掉.
答案即(res = k - sumlimits_{i=1}^k (leftlfloor dfrac{A+C*i}{D} ight floor - leftlfloor dfrac{A+B*i-1}{D} ight floor)).这要求我们在(O(log(D)))的时间内求出形如(sumlimits_{i=0}^nleftlfloor dfrac{a+bi}{c} ight floor)的式子,这部分是类欧几里得算法的板子,参考oi-wiki或洛谷.
我的代码具体实现的时候,板子是从(0)开始计算的,所以计算结果挖掉了(i=0)的情况.
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
ll f(ll a, ll b, ll c, ll n)
{
if (a == 0)
return (n + 1) * (b / c);
if (a >= c || b >= c)
return (f(a % c, b % c, c, n) + (a / c) * n * (n + 1) / 2 + (b / c) * (n + 1));
ll m = (a * n + b) / c;
return (n * m - f(c, c - b - 1, a, m - 1));
}
int main()
{
int T;scanf("%d",&T);
while(T--)
{
ll a,b,c,d;scanf("%lld%lld%lld%lld",&a,&b,&c,&d);
ll k = (d - 2) / (c - b);
ll res = k;
ll lf = f(c,a,d,k) - a / d;
ll rt = f(b,a - 1,d,k) - (a - 1) / d;
res -= lf - rt;
printf("%lld
",res);
}
return 0;
}