题目链接:
题目大意:
给出两个数a,b,k有两种操作,a-=1,或者a-=a%x(2<=x<=k),问最少需要多少步操作能够使a变成b
题目分析:
- 首先我们考虑数据范围小的怎么做,定义状态dp[i]表示从a到达i最少需要多少步
- {dp[i−1]=min(dp[i−1],dp[i]+1)dp[i−i%x]=min{(dp[i−i%x],dp[i]+1)|2≤x≤k}
- 那么我们考虑题目给出的数据范围好大,直接做肯定超时,那么考虑每步操作只有a-=1和a-=a%x,那么我们考虑每次决策如果不选1的话,那么可能会更快的到达,但是如果所有的x都会被整除,那么选择1反而会得到最优解,所有的x都被整除,那么此时的数一定是所有x的lcm的倍数。
{a=p⋅lcm+ba=p1⋅x1+b1⇒a=p⋅lcm+p3⋅x1+b1⇒a−b1=p⋅lcm+p3⋅x1⇒a−b1=p⋅lcm+q ( q=p3⋅x1)⇒a−b1=p⋅lcm+p4⋅x2+b2⇒a−b1−b2=p⋅lcm+p4⋅x2同理可得,⇒a−∑i=1nbi=p⋅lcm+pn⋅xn
所以每个lcm都是可达的,且一定要到达的,因为通过如上操作是不能跳过某一个lcm的倍数的,所以我们利用lcm分块,然后只需要根据性质,对相同性质的块操作只做一次就可以了。
AC代码:
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#define MAX 400007
using namespace std;
typedef long long LL;
LL a,b,dp[MAX];
int k;
LL gcd ( LL a , LL b )
{
return b==0?a:gcd(b,a%b);
}
LL solve ( LL a , LL b )
{
memset ( dp, 0x3f , sizeof ( dp ) );
dp[a] = 0;
for ( int i = a ; i > b ; i-- )
{
dp[i-1] = min( dp[i]+1 , dp[i-1] );
for ( int j = 2 ; j <= k ; j++ )
dp[i-i%j] = min ( dp[i-i%j] , dp[i]+1 );
}
return dp[b];
}
int main ( )
{
while ( ~scanf ( "%lld%lld%d" , &a , &b , &k ) )
{
LL lcm = 2;
for ( int i = 3; i <= k ; i++ )
{
int d = gcd ( i , lcm );
lcm *= i;
lcm /= d;
}
LL ans;
if ( a/lcm == b/lcm )
ans = solve ( a%lcm , b%lcm );
else
ans = solve ( a%lcm , 0 ) + (a/lcm-b/lcm-1)*solve ( lcm , 0 ) + solve ( lcm , b%lcm );
printf ( "%lld\n" , ans );
}
}