[典藏题记001]D. Frog Jumping(数论+图论+思维+暴力)

    技术2024-07-21  75

    题目传送门 写这题脑细胞快死完了,看了大佬的题解又研究了半天。 简单讲一下我的做法: 这题可以小范围暴力,大范围找出规律。 设 h ( i ) 为 要 到 达 点 i 的 最 小 x 设h(i)为要到达点i的最小x h(i)ix。 那么 a n s = ans= ans= ∑ i = 0 n ( n − h ( i ) + 1 ) ( 如 果 点 i 可 以 到 达 ) \sum\limits_{i=0}^{n}{(n-h(i)+1)}(如果点i可以到达) i=0n(nh(i)+1)(i)

    然后有一个性质就是: 如果点i可达,那么一定满足: i ∣ g c d ( a , b ) i|gcd(a,b) igcd(a,b)

    即对于x>a+b,可以保证 h ( i ) = i h(i)=i h(i)=i , 对 于 ( i ∣ g c d ( a , b ) ) 的 点 ,对于(i|gcd(a,b))的点 ,(igcd(a,b)) 想象一个长度为 a + b + 1 a+b+1 a+b+1的线段,线段上的每一个点要么可以往左,要么可以往右,两者可以同时成立,但不可能都不成立。在总共经历了 a / ( g c d ( a , b ) ) a/(gcd(a,b)) a/(gcd(a,b))次后跳和一定次数前跳后,一定可以到达点 i i i. 这里用到了一个结论:就是两个互质的数a和b,( a + i ∗ b ( 0 < = i < a ) ) a+i*b(0<=i<a)) a+ib(0<=i<a))中的每个数对a的模数都不一样,然后其余 i i i的取值根据鸽巢定理显然会造成重复。 因为取模时间复杂度很高,这里我们要用公式一次求出这部分的答案。

    对于 x < = a + b x<=a+b x<=a+b的部分暴力求解即可。我这里用了 d i j k s t r a dijkstra dijkstra堆优化来进行每个点的松弛操作。

    #include<bits/stdc++.h> using namespace std; #define ll long long #define MAXN 200005 ll a,b; ll n,m; int dis[MAXN]; int inf=2e9; struct node{ ll val; ll x; bool operator <(const node&a)const{ if(a.val==val) return x>a.x; return val>a.val; } }; inline int check(int x) { if(x>=0&&x<=m)return 1; return 0; } int main() { ll ans=0; priority_queue<struct node>q; scanf("%lld%lld%lld",&n,&a,&b); m=min(n,a+b); for(int i=1;i<=m;i++) dis[i]=inf; for(int i=0;i<=m;i+=a) { dis[i] = i; q.push({dis[i],i}); } while(!q.empty()) { node now = q.top(); q.pop(); ll x = now.x; if (check(x + a)) { if (max(dis[x ],(int)(x+a)) < dis[x+a]) { dis[x + a] = max(dis[x],(int)(x+a)); q.push({dis[x + a], x + a}); } } if (check(x - b)) { if (dis[x - b] > dis[x]) { dis[x - b] = dis[x]; q.push({dis[x - b], x - b}); } } } for(int i=0;i<=m;i++) if(dis[i]!=inf) ans+=(n-dis[i]+1); if(n>a+b) { ll g=__gcd(a,b); ll c=a+b+1; c=c-c%g+g-g*(c%g==0); ll d=n-n%g; ll sum=(n-d+1+n-c+1)*((d-c+g)/g)/2; ans+=sum; } cout<<ans<<endl; }
    Processed: 0.045, SQL: 9