动态规划与回溯法的练习小记

动态规划与回溯法的练习小记

动态规划-一星:互不攻击的士兵

Description
你有一个含n个格子的一维棋盘。你可以往棋盘上放任意多个士兵。(每个格子只能放置一个士兵)

一个士兵会攻击与他相邻的左右两个格子中的士兵。

现在问你,有多少种放置的方案使得棋盘上的士兵互不攻击?

Input
一个整数n(1 <= n <= 40),代表一维棋盘的格子数.

Output
输出一个整数,代表可行的方案数.保证答案在int32范围之内

Sample Input 1
2
Sample Output 1
3
Hint
对于样例,有两个格子,将其标号为1 , 2.

有三种放置方案.

第一种:一个士兵也不放,这是可行的

第二种:在1号棋盘上放置一个士兵.

第三种:在2号棋盘上放置一个士兵.

注意:不能同时在 1 , 2 号棋盘上放置士兵.由于相邻,他们会互相攻击.

就是简单的斐波拉契有递归和迭代两种

递归版

#include <iostream>
using namespace std;

int fun(int n)
{
    if (n==1)
    {
        return 2;
    }
    if (n==2)
    {
        return 3;
    }
    if (n==3)
    {
        return 5;
    }
    return fun(n-1)+fun(n-2);
}

int main()
{
    int n;
    cin>>n;
    cout<<fun(n)<<endl;
    return 0;
}

耗时较大
在这里插入图片描述
迭代版

#include <iostream>
using namespace std;

//int fun(int n)
//{
//    if (n==1)
//    {
//        return 2;
//    }
//    if (n==2)
//    {
//        return 3;
//    }
//    if (n==3)
//    {
//        return 5;
//    }
//    return fun(n-1)+fun(n-2);
//}

int main()
{
    int n,m[41]={0,2,3};
    cin>>n;
    for (int i=3;i<=n;i++)
    {
        m[i]=m[i-1]+m[i-2];
    }
    cout<<m[n]<<endl;
    return 0;
}

在递归的基础上大幅减少了耗时

动态规划-二星:老千层饼

Description
昌子哥是个老千层饼了,有一天,塔子哥和昌子哥比赛从第1层开始,看谁能更快到达第m层,塔塔的塔子哥居然一层一层往下走,这显然太慢了,为了公平起见,于是昌子哥给出了多个单向快捷通道,可以只使用1s就能从第u层到达第v层。已知到相邻的层需要1s,可以前进到下一层也可以回退到上一层,只能在【1,m】层之间转移,即不能到0或m+1层等层数。请你帮助塔子哥计算出到达第m层所需要的最少时间,战胜昌子哥。

提示:根据题目意思建立图模型,可以发现题目本质上是让我们求解两点间的最短路。由于数据量很小,方法十分多.

Input
第一行两个数n,m。表示有n个快捷通道,目的地是第m层。

接下来n行,每行给出u,v。表示从第u层到第v层有一个快捷通道。

0<=n,m<=200

0<u,v<=m

Output
从第1层出发到达第m层所需要的最少时间。

Sample Input 1
4 16
2 10
8 15
12 5
13 6
Sample Output 1
6
Hint
最优方案为:1–>2–>10–>9–>8–>15–>16

此问题可以看成一个图的问题,而单向快捷通道可以看成连通了图的那个两个节点,因为耗时都是1秒,所以可以看成全部是权重为1的路径,所以次问题可以看成求最短路径的问题。采用弗洛伊德算法或者迪杰斯特拉可求出。

Floyd:

#include<iostream>

#define INF 9999
using namespace std;

int dp[202][202] = {0};

int main()
{
    int n, m;
    cin >> n >> m;
    for (int i = 0; i < 201; i++)
    {
        for (int j = 0; j < 201; j++)
        {
            if (i + 1 == j || i - 1 == j)
                dp[i][j] = 1;
            else
                dp[i][j] = INF;
        }
    }
    int i, j;
    while (n--)
    {
        cin >> i >> j;
        dp[i][j] = 1;
    }
    for (int k = 1; k <= m; k++)
    {
        for (int i = 1; i <= m; i++)
        {
            for (int j = 1; j <= m; j++)
            {
                if (dp[i][j] > dp[i][k] + dp[k][j])
                {
                    dp[i][j] = dp[i][k] + dp[k][j];
                 }
             }
         }
     }

    cout << dp[1][m]<<endl;
    return 0;
}

回溯法-一星:恶心塔子哥

Description
塔子哥太讨厌吃素了,请你帮忙恶心他,找出长为n的,由1,2,…,n组成的一个环,环中所有相邻的数的和都为素数的环个数。(注意,如1,2,3,4和2,3,4,1是同一个环,只计算一次,但是1,2,3,4和1,4,3,2却不算是同一个环)

Input
第一行一个数n,表示找出长为n的,由1,2,…,n组成的一个环,该环以1开头,环中所有相邻的数的和都为素数的环个数。(2<=n<=18)

Output
找出长为n的,由1,2,…,n组成的一个环,该环以1开头,环中所有相邻的数的和都为素数的环个数。

Sample Input 1
6
Sample Output 1
2

在1到n的全排列中以1开头的组合验证两数之和是否为素数,注意枝剪函数

超时代码:

#include <cmath>
#include <iostream>
using namespace std;

int m=0;

bool prime_number(int n)
{
    for (int i = 2; i <= sqrt(n); i++)
    {
        if (n % i == 0)
        {
            return false;
        }
    }
    return true;
}

void print(int a[],int n)
{
    for (int i=1;i<=n;i++)
    {
        cout<<a[i]<<' ';
    }
    cout<<endl;
}

void dfs(int a[],int flag[],int n, int step)
{
    if (step==n+1)
    {
        if (a[1]==1)
        {
            a[n+1]=1;
            int t=1;
            for (int i = 1; i <= n; ++i)
            {
                if (!prime_number(a[i]+a[i+1]))
                {
                    t=0;
                    break;
                }
            }
            if (t==1)
            {
                m++;
            }
        }
    }
    for (int i=1;i<=n;i++)
    {
        if(flag[i]==0&&prime_number(i+a[step-1]))
        {
            a[step]=i;
            if (a[step]==0)
            {
                cout<<step<<endl;
            }
            flag[i]=1;
            dfs(a,flag,n,step+1);
            flag[i]=0;
        }
    }
}

int main()
{
    int n,a[20]={0},flag[19]={0},step=1;
    cin>>n;
    dfs(a,flag,n,step);
    cout<<m<<endl;
}

AC代码:

#include <cmath>
#include <iostream>
//#include <ctime>
using namespace std;

int m=0;

bool prime_number(int n)
{
    for (int i = 2; i <= sqrt(n); i++)
    {
        if (n % i == 0)
        {
            return false;
        }
    }
    return true;
}

//void print(int a[],int n)
//{
//    for (int i=1;i<=n;i++)
//    {
//        cout<<a[i]<<' ';
//    }
//    cout<<endl;
//}

void dfs(int a[],int flag[],int n, int step, bool prime[])
{
    if (step==n+1)
    {
        if (a[1]==1)
        {
            a[n+1]=1;
            int t=1;
            for (int i = 1; i <= n; ++i)
            {
                if (!prime[a[i]+a[i+1]])
                {
                    t=0;
                    break;
                }
            }
            if (t==1)
            {
                m++;
            }
        }
    }
    for (int i=2;i<=n;i++)
    {
        if(flag[i]==0&&prime[i+a[step-1]])
        {
            a[step]=i;
//            if (a[step]==0)
//            {
//                cout<<step<<endl;
//            }
            flag[i]=1;
            dfs(a,flag,n,step+1,prime);
            flag[i]=0;
        }
    }
}

int main()
{

    bool prime[40];
    int n,a[20]={0,1},flag[19]={0},step=2;
    cin>>n;
    for (int i=1;i<=2*n;i++)
    {
        prime[i]=prime_number(i);
    }
    dfs(a,flag,n,step,prime);
    cout<<m<<endl;
//    cout << "The run time is:" << (double)clock() /CLOCKS_PER_SEC<< "s" << endl;
}

最佳耗时代码

#include <iostream>

using namespace std;

int main()
{
    int n, m;
    cin >> n;
    if (n % 2 == 1)
    {
        m = 0;
    }
    else if (n == 2) m = 1;
    else if (n == 4 || n == 6) m = 2;
    else if (n == 8) m = 4;
    else if (n == 10) m = 96;
    else if (n == 12) m = 1024;
    else if (n == 14) m = 2880;
    else if (n == 16) m = 81024;
    else if (n == 18) m = 770144;
    cout << m;
}

回溯法-二星:分糖果咯

Description
菜菜最近上网课,非常无聊,天天在家和一群小朋友玩,但是菜菜是个不好玩的人,小朋友都不愿意和他玩,菜菜最近想到了一个好办法,他想给小朋友买糖果,但是小朋友对每种糖果的需求是不同的,也就是说,每个小朋友的口味不同,他想在满足小朋友的同时,使得自己买的糖果数量最少,但是同时不能买相同的糖果,因为这样小朋友会觉得糖果一样的,没什么意思,菜菜是个穷鬼,你能写个程序帮助他使得他买的糖果数量最少吗?

Input
第一行一个数n,同时代表小朋友的人数和糖果的种类数

之后的n行,每行n个数字,代表每个小朋友对每种糖果的需求。

Output
一个数n,代表菜菜要买的糖果个数。

Sample Input 1
5
50 43 1 58 60
87 22 5 62 71
62 98 97 27 38
56 57 96 73 71
92 36 43 27 95
Sample Output 1
144

Hint
样例描述:上述样例选第一行的1,第二行的22,第三行的38,第四行的56,第五行的27.使得买的糖果的数量最少.

数据范围在int范围内,n<=10.

此问题可以采用回溯法来解决,其回溯法的解空间为每一列其中一个数其他几列且不同行的其中一个数的组合。约束条件每一列只能选一个数,且其他列选的数不能与其同行。为了避免同行可以采用一个vis的布尔变量数组来进行选取。对于求满足约束条件的最小值可以通过回溯来计算。

#include <iostream>
using namespace std;
int a[10001][10001],n,minn=0x7fffff,maxn=-0x7fffff;
bool vis[10001];

void dfs(int dep,int val)
{
    if(dep>n)
    {
        minn=min(minn,val);
        return ;
    }
    for(int i=1;i<=n;++i)
    {
        if(!vis[i])
        {
            vis[i]=1;
            dfs(dep+1,val+a[dep][i]);
            vis[i]=0;
        }
    }
}

int main()
{
    cin>>n;
    int i,j;
    for(i=1;i<=n;++i)
    {
        for(j=1;j<=n;++j)
        {
            cin>>a[i][j];
        }
    }
    dfs(1,0);
    cout<<minn<<endl;
    return 0;
}

回溯法-三星:完美数

Description
我们将一个十进制正整数N定义为完美数当且仅当 任意两个相邻的数位的 差的绝对值不超过1.

形式化定义为: 对于 N = d1d2d3d4…dm. 对于 ∀i∈[1 , m) 有 |di - d(i + 1)| <= 1.

例如: 111 . 777 . 123. 322 都是完美数,而 115 . 224. 124 都不是完美数.

现在塔子哥想知道,第K小完美数是多少.(1 <= k <= 100000)

Input
第一行为一个数T(1 <= T <= 10000),代表接下来有T组测试样例。

接下来的T行,每一行一个整数K,含义如题目所示.

Output
对于每组测试样例,单独输出一行整数N,代表第K小的完美数(注意:每个输出结果独占一行)

Sample Input 1
4
1
2
3
13
Sample Output 1
1
2
3
21
Hint
前13小的完美数为: 1,2,3,4,5,6,7,8,9,10,11,12,21

从个位看起,多一位则为此时数字的个位与之匹配的数之差的绝对值小于2的数加上此数乘以10

暴力解法超时:

#include <iostream>
#include <cmath>

using namespace std;
int a[9999999] = {0},m,t;

bool judge(int n)
{
    int a[100] = {0}, i;
    for (i = 0; n; i++)
    {
        a[i] = n % 10;
        n /= 10;
    }
    for (int j = 0; j < i; ++j)
    {
        if (j + 1 < i)
        {
            if (abs(a[j] - a[j + 1]) > 1)
            {
                return false;
            }
        }
    }
    return true;
}

void fun(int n)
{
//    int t = 0;
    if (n<=m)
    {
        cout<<a[n]<<endl;
        return;
    }
    for (int i = a[m]+1;; i++)
    {
        if (judge(i))
        {
            t++;
            a[t] = i;
//            cout<<"a["<<t<<"]="<<a[t]<<endl;
            if (t == n)
            {
                m=t;
                cout << a[t] << endl;
                return;
            }
        }
    }
}

int main()
{
    int n, m;
//    clock_t startTime,endTime;
    cin >> n;
    while (n--)
    {
        cin >> m;
//        startTime = clock();//计时开始
        fun(m);
//        endTime = clock();//计时结束
//        cout << "The run time is:" <<(double)(endTime - startTime) / CLOCKS_PER_SEC << "s" << endl;
    }
    return 0;
}

AC代码

#include<iostream>

using namespace std;

int main()
{
    int q = 0, p = 9, a[100005] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
    while (p < 100005)
    {
        int m = a[q] % 10;
        for (int i = 0; i < 10; i++)
        {
            if (abs(m - i) < 2)
            {
                a[p++] = a[q] * 10 + i;
            }
        }
        q++;
    }
    int n, m;
    cin >> n;
    while (n--)
    {
        cin >> m;
        cout << a[m - 1] << endl;
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Gowi_fly

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值