2024牛客暑假训练营2 ( A B C E H I 题) ✨✨✨
A题
题目大意:在n*m的格子上面放置A,B两种地砖,是否能够拼成具有k条线的图形
地砖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 2⋅n 张卡片排成一行,每张卡片的编号从 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;
}