我们设(h(i)),表示要到达点(i),最远必须经过(y)。特别地,如果永远无法到达(i),令(h(x)=inf)。那么,每个位置(i),会在( ext{lim}in[h(i),m])时成为一个能够到达的位置(其中( ext{lim})表示可以走的范围为([0, ext{lim}]))。因此最终的答案就是(sum_{i=0}^{m}max(0,m-h(i)+1))。
显然,点(i)能够在有限步之内到达((h(i) eq inf))的一个必要条件是,方程(ax+by=i)有整数解。根据裴蜀定理,该条件等价于(i)是(gcd(a,b))的倍数。不难猜到,当(i)比较大时,如果(i)是(gcd(a,b))的倍数,那么(h(i)=i)。因为我可以在走的过程中(例如需要走(x)步向前的,(y)步向后的),那么只要当前坐标(geq b),且向后退的(y)步没用完,我就先往回退。根据这个策略,不难发现,在用完所有往回退的步数之前,我使用的坐标范围不会超过([0,a+b])。这也就对应了前面我们猜想中的“(i)比较大”,其实就是指(i>a+b)时:
[egin{cases}
h(i)=i&&gcd(a,b)|i\
h(i)=inf&& ext{otherwise}
end{cases}
]
因此,对于(i>a+b)的部分,对答案的贡献就是:
[egin{align}
&sum_{i=a+b+1}^{m}[gcd(a,b)|i](m-i+1)\
=&sum_{i=lceilfrac{a+b+1}{gcd(a,b)}
ceil}^{lfloorfrac{m}{gcd(a,b)}
floor}(m-gcd(a,b)cdot i+1)
end{align}
]
可以用等差数列求和的公式(O(1))计算这部分的贡献。
当(i<a+b)的时候,不一定能够在(a+b)之间自由挪动,所以并不能确定(h(i))具体等于多少。但是,如果确定了某个(h(j)),则可以用(h(j))来更新(h(j-b))和(h(j+a))的值。这很像求最短路时的“松弛”操作。所以可以用dijkstra来实现这一过程,暴力算出前(a+b)个(h)值。起点是(h(0)=0)。时间复杂度(O((a+b)log (a+b)))。
参考代码:
//problem:CF1146D
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
const int MAXN=2e5,INF=0x3f3f3f3f;
int m,a,b,h[MAXN+5];
int gcd(int x,int y){return !y?x:gcd(y,x%y);}
int main() {
cin>>m>>a>>b;
memset(h,0x3f,sizeof(h));
priority_queue<pii>q;
h[0]=0;
q.push(mk(0,0));
while(!q.empty()){
pii cur=q.top();
q.pop();
if(-cur.fi>h[cur.se])continue;
if(cur.se>b&&h[cur.se-b]>h[cur.se]){
h[cur.se-b]=h[cur.se];
q.push(mk(-h[cur.se-b],cur.se-b));
}
if(cur.se<=b&&h[cur.se+a]>max(h[cur.se],cur.se+a)){
h[cur.se+a]=max(h[cur.se],cur.se+a);
q.push(mk(-h[cur.se+a],cur.se+a));
}
}
ll ans=0;
for(int i=0;i<=min(m,a+b);++i){
ans+=max(0,m-h[i]+1);
}
if(m>a+b){
ll g=gcd(a,b);
ll lo=(a+b+1)/g+((a+b+1)%g!=0);
ll hi=m/g;
if(lo<=hi){
ans+=(hi-lo+1)*(m+1)-(lo*g+hi*g)*(hi-lo+1)/2;
}
}
cout<<ans<<endl;
return 0;
}