题解 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\)时:

\[\begin{cases} h(i)=i&&\gcd(a,b)|i\\ h(i)=\inf&&\text{otherwise} \end{cases} \]

因此,对于\(i>a+b\)的部分,对答案的贡献就是:

\[\begin{align} &\sum_{i=a+b+1}^{m}[\gcd(a,b)|i](m-i+1)\\ =&\sum_{i=\lceil\frac{a+b+1}{\gcd(a,b)}\rceil}^{\lfloor\frac{m}{\gcd(a,b)}\rfloor}(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;
}
posted @ 2020-04-25 22:04  duyiblue  阅读(219)  评论(0编辑  收藏  举报