NEUQ-ACM2022预备队 2023春 第一周

第一周训练

训练主要内容:动态规划、模拟

第一题 特殊的正方形

题目链接

题目1

生成由+和.组成的小正方形隔行排列形成的正方形

思路1

写一层循环,用于循环每层,次数与边长相等。
层数是奇数时说明这一层是+,是偶数时说明这一层是.。
在循环内为填写属于该小正方形的字符。

代码1

#include<iostream>
using namespace std;
char c[101][101];
int main()
{
    int n;
    cin >> n;
    //外层循环
    for (int i = 1; i <= n; i++)
    {
        //判断奇偶数
        char t = i % 2 == 0 ? '.' : '+';
        //填写每个小正方形的四条边
        for (int j = i; j <= n - i + 1; j++)
        {
            c[i][j] = t;
            c[j][i] = t;
            c[n - i + 1][j] = t;
            c[j][n - i + 1] = t;
        }
    }
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            cout << c[i][j];
        }
        cout << endl;
    }
    return 0;
}

第二题 走楼梯2

题目链接

题目2

一次可以走一级或两级楼梯,连续最多走2次两级楼梯,问走到第n级有多少种走法。

思路2

走楼梯的种类是常见的动态规划的题目。
状态:走到第i级和连续走了两级j次。
状态转移方程:f(i, 0) = f(i - 1, 0 / 1 / 2); f(i, 1) = f(i - 2, 0); f(i, 2) = f(i - 2, 1)
初始条件:走上第一级有一种方案。

代码2

#include<iostream>
using namespace std;
//存放状态,第一维是第i级,第二维是走了两级j次
long long ans[52][4];
int n;

int main()
{
    cin >> n;
    //初始条件
    ans[0][0] = 1;
    ans[1][0] = 1;
    for (int i = 2; i <= n; i++)
    {
        //状态转移方程
        ans[i][0] += (ans[i - 1][0] + ans[i - 1][1] + ans[i - 1][2]);
        ans[i][1] += ans[i - 2][0];
        ans[i][2] += ans[i - 2][1];
    }
    cout << ans[n][0] + ans[n][1] + ans[n][2] << endl;
    return 0;
}

第三题 走路

题目链接

题目3

你位于0的位置,接下来n次可以向右走ai或者bi步,问n次之后能走到的位置。

思路3

模拟题。用一个数组存放0 - m所有点是否能走到,每次走的时候按照前次结果寻找这次能走到的地方。
因为只涉及了两次的结果,所以可以用滚动数组的方法存放能走到的位置。

代码3

#include<iostream>
#include<cstring>
using namespace std;
//m最大值
constexpr int MMax = 1e5 + 5;
//能走到的位置,第二维的2表示最近两次的结果
bool a[MMax][2];
int n, m;
int ai, bi;

int main()
{
    cin >> n >> m;
    //初始位置
    a[0][0] = true;
    for (int i = 1; i <= n; i++)
    {
        cin >> ai>> bi;
        //判断前一次存放的位置
        int p = i % 2;
        //判断这一次存放的位置
        int pp = (p == 0) ? 1 : 0;
        //遍历所有前一次走到的点,推算这次能走到的点
        for (int j = 0; j <= m; j++)
        {
            if (a[j][pp])
            {
                a[j + ai][p] = true;
                a[j + bi][p] = true;
            }
        }
        //推算完,将前一次的数据清空,防止影响下一次存储
        for (int j = 0; j <= m; j++)
        {
            a[j][pp] = false;
        }
    }
    //输出
    int p = n % 2;
    for (int i = 0; i <= m; i++)
    {
        cout << a[i][p] ? 1 : 0;
    }
    return 0;
}

第四题 简单分数统计

题目链接

题目4

有n个人参加m个题目的比赛,需要通过平台的提交记录(包含别的人和别的题目)判分。

思路4

依然是简单的模拟。可以用map存放每个人得的分和每到题的分值。遇到名字和题目都存在的记录就计分。

代码4

#include<iostream>
#include<map>
#include<vector>
using namespace std;

int main()
{
    //题目的分值
    map<string, int> qscore;
    //学生的分数
    map<string, int> sscore;
    //所有的学生,用于最后的输出
    vector<string> idlist;
    int n, m, k;

    cin >> n >> m >> k;
    //存放学生
    for (int i = 0; i < n; i++)
    {
        string id;
        cin >> id;
        //每个学生初始分数为0
        sscore.insert(map<string, int>::value_type(id, 0));
        idlist.push_back(id);
    }
    //存放题目
    for (int i = 0; i < m; i++)
    {
        string q;
        int score;
        cin >> q >> score;
        qscore.insert(map<string, int>::value_type(q, score));
    }
    //读取记录
    for (int i = 0; i < k; i++)
    {
        string s, q, r;
        cin >> s >> q >> r;
        //学生存在、题目存在并且答案正确,才需要计分
        if (sscore.find(s) != sscore.end() && qscore.find(q) != qscore.end() && r == "AC")
        {
            sscore[s] += qscore[q];
        }
    }
    //逐个输出
    for (int i = 0; i < n; i++)
    {
        cout << idlist[i] << ' ' << sscore[idlist[i]] << endl;
    }
    return 0;
}

第五题 Alice的德州扑克

题目链接

题目5

有6种牌型,需要判断牌型。

思路5

按照不同牌型的要求判断即可。

代码5

#include<iostream>
using namespace std;

int main()
{
    int point[5], suit[5];
    for (int i = 0; i < 5; i++)
    {
        cin >> point[i];
    }
    for (int i = 0; i < 5; i++)
    {
        cin >> suit[i];
    }
    //同花顺
    if (point[0] == point[1] - 1 && point[1] == point[2] - 1 && point[2] == point[3] - 1 && point[3] == point[4] - 1
    && suit[0] == suit[1] && suit[1] == suit[2] && suit[2] == suit[3] && suit[3] == suit[4])
    {
        //判断是否为皇家同花顺
        if (point[4] == 14)
        {
            cout << "ROYAL FLUSH" << endl;
        }
        else
        {
            cout << "STRAIGHT FLUSH" << endl;
        }
    }
    //四条,只有两种情况:前4张点数相同或后4张点数相同
    else if ((point[0] == point[1] && point[1] == point[2] && point[2] == point[3]) || (point[1] == point[2] && point[2] == point[3] && point[3] == point[4]))
    {
        cout << "FOUR OF A KIND" << endl;
    }
    //葫芦,只有两种情况:前三张和后两张分别点数相同或前两张和后三张分别点数相同
    else if ((point[0] == point[1] && point[1] == point[2] && point[3] == point[4]) || (point[2] == point[3] && point[3] == point[4]) && point[0] == point[1])
    {
        cout << "FULL HOUSE" << endl;
    }
    //同花
    else if (suit[0] == suit[1] && suit[1] == suit[2] && suit[2] == suit[3] && suit[3] == suit[4])
    {
        cout << "FLUSH" << endl;
    }
    //顺子
    else if (point[0] == point[1] - 1 && point[1] == point[2] - 1 && point[2] == point[3] - 1 && point[3] == point[4] - 1)
    {
        cout << "STRAIGHT" << endl;
    }
    //其余情况
    else
    {
        cout << "FOLD" << endl; 
    }

    return 0;
}

第六题 任务分配

题目链接

题目6

有n个任务,有不同的开始时间和结束时间,还有收益。问怎样收益最大。

思路6

是一道动态规划。
状态:结束时间i
状态转移方程:f(i) = max(f(si) + wi(si和wi是每一个在i时结束的任务), f(i - 1))

代码6

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

constexpr int maxn = 1e3 + 1;
constexpr int maxt = 1e3 + 1;
int n;
int si[maxn], ei[maxn], wi[maxn];
int ans[maxt];

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> si[i] >> ei[i] >> wi[i];
    }
    //状态i
    for (int i = 1; i <= 1000; i++)
    {
        //在执行到此处时已经能保证,i时刻已经结束的情况都已考虑
        //只需要再考虑i时刻没有任务的情况
        ans[i] = max(ans[i], ans[i - 1]);
        //遍历在i时刻开始的任务,更新在i时刻之后的状态
        for (int j = 1; j <= n; j++)
        {
            if (si[j] == i)
            {
                ans[ei[j]] = max(ans[ei[j]], ans[i] + wi[j]);
            }
        }
    }
    cout << ans[1000] << endl;
    return 0;
}

第七题 路径计数

题目链接

题目7

一个网格上,有些格子可以通行。问从左上角走到左下角的路径数。

思路7

还是动态规划的题目。
状态:位置(i, j)
状态转移方程:f(i, j) = f(i - 1, j) + f(i, j - 1)
起始条件:f(1, 1) = 1

代码7

#include<iostream>
using namespace std;

//最大边长和取模值
constexpr int maxn = 102, mod = 1e9 + 7;
int n;
//存放网格状态
bool map[maxn][maxn];
//存放dp结果
int ans[maxn][maxn];

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            cin >> map[i][j];
        }
    }
    //起始条件
    ans[1][1] = 1;
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            //(1, 1)时已有结果
            if (i == 1 && j == 1)
            {
                continue;
            }
            //该点不能通过
            if (!map[i][j])
            {
                continue;
            }
            //左侧边,只能从上面走
            else if (j == 1)
            {
                ans[i][j] += ans[i - 1][j];
            }
            //上侧边,只能从左面走
            else if (i == 1)
            {
                ans[i][j] += ans[i][j - 1];
            }
            //普通情况
            else
            {
                ans[i][j] += ans[i][j - 1] + ans[i - 1][j];
            }
            ans[i][j] %= mod;
        }
    }
    cout << ans[n][n] << endl;
    return 0;
}

第八题 最大和上升子序列

题目链接

题目8

求一组数中和最大的上升的子序列。(子序列:按原顺序选出一组数组成的新序列)

思路8

最x的子序列都是属于动态规划。
状态:最后一个数是第i个数
状态转移方程:f(i) = max(f(j) + ai (j在i前且aj < ai), ai)
起始条件:f(1) = a1

代码8

#include<iostream>
using namespace std;

constexpr int maxn = 1001;
int a[maxn];
int ans[maxn];
int maxans = 0;
int n;

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    //起始条件
    ans[1] = a[1];
    for (int i = 2; i <= n; i++)
    {
        //寻找每一个在i前的j,并且ai > aj
        for (int j = 1; j < i; j++)
        {
            if (a[i] > a[j])
            {
                //更新
                ans[i] = max(ans[i], ans[j] + a[i]);
            }
        }
        //还有一种情况是从当前数开始一个新序列
        ans[i] = max(ans[i], a[i]);
        maxans = max(ans[i], maxans);
    }
    cout << maxans << endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值