2024牛客暑假训练营2( A B C E H I 题 )代码易看,新手友好✨✨✨

2024牛客暑假训练营2 ( A B C E H I 题) ✨✨✨



A题

题目大意:在n*m的格子上面放置A,B两种地砖,是否能够拼成具有k条线的图形
BA
地砖A和地砖B
具体题目:题目链接

分析:
这个题如果想要写的简洁就得找规律,但是不太好找,不然就要写的又臭又长还大概率 WA
我大概整理一下思路就把题解发出来,先发一个大概的思路


在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


B题(明后天出)

题目大意:给定图, q次询问,每次给出一个点集,求解该点集的最小生成树
具体题目:题目链接

分析:


C题 (签到题)

题目大意:给一个 2*n 的图,有的地方可以走,有的地方不能走,走过的地方不能再走,问从任意起点开始走的最远的步数
具体题目:题目链接

分析:
考点: dp
这里先规定一个走的方向,从右往左走
然后考虑每一个格子的状态怎么规定和变化,这里一列一列从右向左遍历
这里注意 => 一个格子为 ‘R’ 和两个格子都为 ‘R’ 时的不同情况

代码:

#include <bits/stdc++.h>
using namespace std;

int n;
string s[2];

const int N = 1e6 + 6;
int dp[2][N];

int main(void)
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n;
    cin >> s[0] >> s[1];
    s[0] = 'W' + s[0];
    s[1] = 'W' + s[1];

    int ans = 0;
    for (int i = 1; i <= n; ++i)
    {
        if (s[0][i] == 'R')
            dp[0][i] = dp[0][i - 1] + 1;

        if (s[1][i] == 'R')
            dp[1][i] = dp[1][i - 1] + 1;

        if (s[0][i] == 'R' && s[1][i] == 'R')
        {
            int step0 = dp[0][i];
            int step1 = dp[1][i];
            dp[0][i] = max(dp[0][i], step1 + 1);
            dp[1][i] = max(dp[1][i], step0 + 1);
        }

        ans = max(ans, max(dp[0][i],  dp[1][i]));
    }
    ans = max(0, ans - 1);
    cout << ans << '\n';
    return 0;
}

E题 (签到题)

题目大意:给一个正整数 x,找出小于 x 的正整数 y,使等式 gcd(x, y) = x ⊕ y 成立
具体题目:题目链接

分析:
这里建议就是,想不出来的可以从数字8~1全部列举一遍,找规律就行
规律:
① 2的幂次不可能满足 => 和比自己小的数 异或 得到的数比自己大
② 对于奇数 x,x-1 就满足该等式
③ 对于偶数 y,以数字14举例,其二进制表示为 1110,满足该等式的数字枚举一下只有12(1100),再比如数字10,二进制为1010,满足该等式的数字枚举一下只有8(1000),这里就发现一个规律,就是去掉最低位的数字1。这样就可以保证得到的数字和其异或得到的一定就是最大公约数(这里想不明白自己推一下)

代码:

#include <bits/stdc++.h>
using namespace std;

using ll = long long; // 注意输入数字范围

int main(void)
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int T;
    cin >> T;
    while (T--)
    {
        ll n;
        cin >> n;
        if ((n & (n - 1)) == 0) // 判断是否为 2 的幂次
            cout << -1 << '\n';
        else
            cout << n - (n & -n) << '\n'; // 减去最低位的 1
    }
    return 0;
}


H题

题目大意:给定一个序列,求有多少子序列的操作结果可以从 (0,0) 通过 (x,y)
具体题目:题目链接

分析:
这个题就是寻找子串,先采用前缀和处理,再枚举端点和范围,但是 n<2*10^5, 这里考虑优化
具体优化思路:
开一个map<pair<int,int>,int>,记录点的位置信息和下标,采用 点x+距离x=目标x 等式来进行二分查找即可
最后,特判一下如果终点即为(0,0)的情况就行

代码:

#include <bits/stdc++.h>
using namespace std;

using ll = long long;

int n, x, y;
string s;

vector<pair<int, int>> arr;
map<pair<int, int>, int> mp;

int main(void)
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n >> x >> y;
    cin >> s;
    s = '0' + s;

    if (x == 0 && y == 0) // 特判一下
    {
        cout << (n + 1) * n / 2 << '\n';
        return 0;
    }

    arr.push_back({0, 0});
    int _x = 0, _y = 0;
    for (int i = 1; i <= n; ++i) // 前缀和
    {
        if (s[i] == 'W')
            arr.push_back({_x, ++_y});
        else if (s[i] == 'A')
            arr.push_back({--_x, _y});
        else if (s[i] == 'S')
            arr.push_back({_x, --_y});
        else if (s[i] == 'D')
            arr.push_back({++_x, _y});
    }

    ll ans = 0;
    int xx, yy;
    mp[arr[n]] = n;
    for (int i = n - 1; i >= 0; --i)
    {
        xx = arr[i].first + x;
        yy = arr[i].second + y;
        if (mp.find({xx, yy}) != mp.end())
            ans += n - mp[{xx, yy}] + 1;
        mp[arr[i]] = i;
    }
    cout << ans << '\n';
    return 0;
}

I题

题目大意: 2 ⋅ n 2\cdot n 2n 张卡片排成一行,每张卡片的编号从 1 1 1 n n n 正好有 2 2 2 份。
每次选择一个连续牌子阵(至少有 2 2 2张牌)移除。所选子阵第一张牌和最后一张牌的编号相同。
这个操作的得分是:牌数乘以第一张牌上的数字,求最终得分的最大值

具体题目:题目链接

分析:
一开始想的是贪心(能写,但是代码会很多),规范一点这里考察的是区间dp
考虑的状态转移是要 取数字 i i i 范围最大值,即在数字 i i i 内部遇到 i i i 更大的数字且其左右区间均在数字 i i i 的区间范围内进行最优化判断,使局部最优上升到全局最优
最后在序列两端加上数字 0 0 0,这个是将整个序列局部数字最优整合在一起的关键,想不明白的可以参考例子 ( 0 ) 112233 ( 0 ) (0)1 1 2 2 3 3(0) (0)112233(0)

代码:

#include <bits/stdc++.h>
using namespace std;

using ll = long long;

const int N = 6e3 + 6;
int arr[N];       // 存序列
int l[N], r[N]; // 记录左右下标
int f[N];       // 记录数字i区域算的最大值
int tmp[N];       // 记录算数字过程

int n;

int main(void)
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n;
    int m = n;
    n = n * 2 + 2; // 让序列前后被0包裹,然后从1开始
    for (int i = 2; i <= n - 1; ++i)
    	cin >> arr[i];

    for (int i = 1; i <= n; ++i) // 记录下该数值的左右下标
    {
        if (!l[arr[i]])
            l[arr[i]] = i;
        else
            r[arr[i]] = i;
    }

    for (int i = m; i >= 0; --i) // 从数字最大的n开始到0
    {
        int L = l[i], R = r[i];
        for (int j = L - 1; j <= R; ++j) // 清空
            tmp[j] = 0;

        for (int j = L; j <= R; ++j)
        {
            tmp[j] = tmp[j - 1] + i;          // 把数字i左右范围里面的数字全部加起来
            if (arr[j] > i && j == r[arr[j]])
            {
                int k = l[arr[j]]; // 找到左边界
                if (k >= L)      // 如果左边界在数字i范围内部
                    tmp[j] = max(tmp[j], tmp[k - 1] + f[arr[j]]);
            }
        }
        f[i] = tmp[R];
    }
    cout << f[0] << '\n';
    return 0;
}

  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值