题解 CF1146D Frog Jumping
我们设\(h(i)\),表示要到达点\(i\),最远必须经过\(h(i)\)。特别地,如果永远无法到达\(i\),令\(h(i)=\inf\)。那么,每个位置\(i\),会在\(\text{lim}\in[h(i),m]\)时成为一个能够到达的位置(其中\(\text{lim}\)表示可以走的范围为\([0,\text{lim}]\))。因此最终的答案就是\(\sum_{i=0}^{m}\max(0,m-h(i)+1)\)。
显然,点\(i\)能够在有限步之内到达(\(h(i)\neq \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\)时:
因此,对于\(i>a+b\)的部分,对答案的贡献就是:
这个用等差数列求和的公式\(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;
}