题目链接:

codeforces 251C


题目大意:

给出两个数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 );
    }
}