2020 年百度之星·程序设计大赛 - 初赛一 1003

2020 年百度之星·程序设计大赛 - 初赛一 Dec (记忆化搜索 或 动态规划)

Problem Description

初始有 a, b 两个正整数,每次可以从中选一个大于 1 的数减 1,最后两个都

会减到 1,我们想知道在过程中两个数互质的次数最多是多少。

Input

第一行一个正整数 test ( 1≤test≤1000000 ) 表示数据组数。

接下来 test 行,每行两个正整数a , b (1≤a,b≤1000)。

Output

对于每组数据,一行一个整数表示答案。

Sample Input

1
2 3

Sample Output

4

样例解释
2 3 -> 1 3 -> 1 2 -> 1 1

Solution1

显然,任意一对( a ,b )都有唯一对应的答案。我们假设如果a=1,那么b可以从b一直减到1,减的过程中一直都是互质的状态,即答案就为b。我就在想是不是可以用递归的思想呢?首先我们知道如果a=1,那么答案就是b。那怎么实现递归呢?**假设递归函数solve(a , b)能够求出输入( a ,b )的解,那么solve(a , b)必是可以由solve(a-1 , b)和solve(a , b-1)求出来。**我们假设以下两种情况:

  • 如果a,b并非互质,那么solve(a , b)=max( solve(a-1 , b) , solve(a , b-1) )
  • 如果a,b互质,那么solve(a , b)=max( solve(a-1 , b) , solve(a , b-1) )+1

综上,递归可以这样写:solve(a , b)=max( solve(a-1 , b) , solve(a , b-1) )+(__gcd(a,b)==1)

既然构造出来递归的算法,那么我们是否可以直接AC了呢?其实我们还必须要考虑时间复杂度,递归算法的时间复杂度普遍是比较高的,test的范围是 ( 1≤test≤1000000 ),test次样例的时间代价时间代价是很高的,会直接TLE。

那么我们可以考虑把递归算出来的结果存储起来,怎么存储呢?最好的存储策略就是二维数组,用二维数组实现记忆化搜索,如果把递归算出来的结果存储起来,那么下次遇到同样的问题就不用再重复递归了,直接从数组里面找答案就好啦,这样直接提升了计算效率。

记忆化搜索即可顺利AC。

Code1

记忆化搜索的代码
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

const int N=400;
int a,b,T;
int ans=-1;
map<pair<int,int>,int>mp;
int v[1005][1005];

template<class T> void qr(T &x)
{
    int f=0;
    x=0;
    char c=getchar();
    for(; !isdigit(c); c=getchar())
        f^=c=='-';
    for(; isdigit(c); c=getchar())
        x=(x<<3)+(x<<1)+(c^48);
    x=f?(-x):x;
}

template<class T> void qw(T x)
{
    if(x>=10)
        qw(x/10);
    putchar(x%10+'0');
}

int solve(int a,int b)
{
    if(a>b)
        swap(a,b);
    if(v[a][b])
        return v[a][b];

    if(a==1)
    {
        v[a][b]=b;
        return v[a][b];
    }
    else
    {
        v[a-1][b]=solve(a-1,b);
        v[a][b-1]=solve(a,b-1);
        v[a][b]= max(v[a-1][b],v[a][b-1])+(__gcd(a,b)==1);
        return v[a][b];
    }
}

int main()
{
#ifdef LOCAL
    freopen("in.txt","r",stdin);
    freopen("out1.txt","w",stdout);
#endif

    for(int i=1; i<1002; i++)
    {
        v[1][i]=v[i][1]=i;
    }

    a=b=1000;
    solve(a,b);

    qr(T);
    //T=1;
    while(T--)
    {
        ans=0;
        qr(a);
        qr(b);
        //scanf("%d%d",&a,&b);
        //a=b=1000;
        if(a>b)swap(a,b);
        if(v[a][b])
            ans=v[a][b];
        else
            ans=solve(a,b);
        //printf("%d %d\n",a,b);
        //printf("%d\n",ans);
        qw(ans);
        puts("");
    }
}

Solution2

记忆化搜索基本能够转化成动态规划的题目。

首先我们假设dp[ i ][ j ] 表示 i 和 j 两个数互质的最多次数 。

Solution1相同,dp [ 1 ][ i ] 和 dp[ i ][ 1 ]都是 i (1<=i<=1000),状态转移方程为 dp[ i ] [j ]=max(dp[ i-1 ][ j ],dp[ i ][ j-1 ])+(__gcd( i, j)==1)

有了状态转移方程,就可以轻松写出动态规划的题目了,代码如下。

Code2

动态规划的代码
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

const int N=400;
int a,b,T;
int ans=-1;
int dp[1005][1005];

template<class T> void qr(T &x)
{
    int f=0;
    x=0;
    char c=getchar();
    for(; !isdigit(c); c=getchar())
        f^=c=='-';
    for(; isdigit(c); c=getchar())
        x=(x<<3)+(x<<1)+(c^48);
    x=f?(-x):x;
}

template<class T> void qw(T x)
{
    if(x>=10)
        qw(x/10);
    putchar(x%10+'0');
}

int main()
{
#ifdef LOCAL
    freopen("in.txt","r",stdin);
    freopen("out1.txt","w",stdout);
#endif

    for(int i=1; i<1002; i++)
    {
        dp[1][i]=dp[i][1]=i;
    }

    for(int i=2; i<1002; i++)
    {
        for(int j=2; j<1002; j++)
            dp[i][j]=max(dp[i-1][j],dp[i][j-1])+(__gcd(i,j)==1);
    }

    qr(T);
    //T=1;
    while(T--)
    {
        ans=0;
        qr(a);
        qr(b);
        //scanf("%d%d",&a,&b);
        //a=b=1000;
        if(a>b)swap(a,b);
        ans=dp[a][b];
        //printf("%d %d\n",a,b);
        //printf("%d\n",ans);
        qw(ans);
        puts("");
    }
}


Summary

其实做完这道题,自己的收获还是不小的。一道算法题从不同的角度出发,可能会有多种解法。这道题既巩固了我记忆化搜索的相关知识,同时用使得我对动态规划的使用更灵活一点了。(也许以后的动态规划题目,我可以先从记忆化搜索出发去找状态转移方程,嘿嘿,或许对动态规划就能有不一样的体验了)

你可能感兴趣的:(算法之路)