Pinely Round 5 (Div. 1 + Div. 2) A-D细解
Pinely Round 5 (Div. 1 + Div. 2)
【题目】
参加cf比赛,分两类div1 div2, div1对所有人rated, div2只对<X的rated。
你可以主动选择在rated的场次加分或者减分,如果当前场次能够rated,当前分为R,则变动后区间为[R-D,R+D],即加减D。
输入一个有1和2组成的字符串,表示参赛顺序
输出最多能参加多少场【rated】的场次。
【题解】
显然分越低 越可能触发rated,所以一直掉分就是了。
【代码】
void solve() {
int R0, X, D, n; cin >> R0 >> X >> D >> n;
string s; cin >> s;
int ans = 0;
for (auto c : s) {
if (c == '1' || c == '2' && R0 < X) {
R0 = max(0, R0 - D);
ans++;
}
}
cout << ans << endl;
}
B. Make Connected
【题目】
给定n*n的矩阵,每个格子初始可能是黑色或者白色。你可以操作任意次【把白格子改为黑格子】
要求最后满足如下两个条件:
- 最终所有黑格子都是连通的
- 任意横向或者纵向不能有三个连续的黑格子。
【题解】
要想让某个格子连向另外的格子可以怎么走?
假设第一次向右,显然下一次必然不能向右,否则会有连续三个横向。
比如
##
所以第二次只能向上或者向下,不妨假设是向下
比如
##
.#
第三次显然不能继续向下 否则会有连续三个纵向
所以要么向左 要么向右
注意这里需要分类
如果向左会形成
##
##
此时再也无法移动
这就是【情况1,矩阵中存在一个唯一的2*2方格】
如果向右会形成
##.
.##
注意后续是循环右下 且无法选择别的方向。
这是【情况2,矩阵中所有黑色块被一个锯齿形覆盖】
注意这个锯齿形的方向可能是正反对角线,更细一点可能是先右再下或者先下再右。
根据这个思路可以直接模拟移动来处理。实际代码我们可以更聪明一点。
- 关于方块的判定
//这个是完全独立的判定
//注意先判断总的黑块必须是四个
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - 1; j++) {
if (grid[i][j] == '#' && grid[i][j + 1] == '#' &&
grid[i + 1][j] == '#' && grid[i + 1][j + 1] == '#') {
cout << "YES" << endl;
return;
}
}
}
另一种判定方式取得所有#位置的i,j各自的最大值和最小值,显然应当有
\(max_i-min_i==1\) && \(max_j-min_j==1\)
- 关于Z字形判定
一个基础知识,如何判定是否在对角线上?
以左上角到右下角的对角线为例
设所有坐标为\(x_i\) \(y_i\)
必然有\(x_i-y_i=0\)
如果这条对角线向右偏移一格呢?
比如
.#..
..#.
...#
....
显然满足\(x_i-y_i=-1\)
所以不论偏移多少的对角线
只要在对角线上应当满足\(d=x_i-y_i\)是同一个值
所谓的z字形,就是两条相邻的斜线
则必然有所有坐标的【差值的差值】为0或者1
比如某个黑块坐标差值为3,则另一个坐标差值必须是2,3或者4
注意 还有反对角线
反对角线易知满足\(x_i+y_i=d\),其它条件相同。
两种对角线满足一种即可。
【代码】
void solve() {
int n; cin >> n;
vector<string> grid(n);
for (int i = 0; i < n; i++) cin >> grid[i];
int cnt = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == '#') {
cnt++;
}
}
}
//如果只有四个并且形成一个方块是可以的
if (cnt == 4) {
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - 1; j++) {
if (grid[i][j] == '#' && grid[i][j + 1] == '#' &&
grid[i + 1][j] == '#' && grid[i + 1][j + 1] == '#') {
cout << "YES" << endl;
return;
}
}
}
}
//检测对角线相邻
int mind1 = INT_MAX, mind2 = INT_MAX, maxd1 = INT_MIN, maxd2 = INT_MIN;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == '#') {
mind1 = min(mind1, j - i);
mind2 = min(mind2, i + j);
maxd1 = max(maxd1, j - i);
maxd2 = max(maxd2, i + j);
}
}
}
if (maxd1 - mind1 <= 1 || maxd2 - mind2 <= 1) {
cout << "YES" << endl;
}
else cout << "NO" << endl;
}
【题目】
给定一个模数X
给定一组商品价格为\(a_i\) 且\(a_i<=X\)
你每买一个商品,累积消费和设为S,每当S/X增加时会得到当前的这个商品价格的忠诚值。
任意选择购买顺序,输出最大忠诚值。
比如[2,3] X=4 初始S/X=0,购买第二个商品时会导致S/X=5/4>0
此时选择第二次买价格为3的商品可以获得最大忠诚度3
【题解】
这个似乎比B要简单。
模型也比较简单了,显然需要再每次升级的最后一次要买当前剩余价格最大的。
简单排序数组,从0开始枚举i,同时维护末尾元素last
如果当前累计和\(S+a_{last}\)会升级,则安排last元素,同时last--
直到所有元素添加进去即可
【代码】
using i64 = long long;
void solve() {
i64 n, x; cin >> n >> x;
vector<i64> nums(n);
for (int i = 0; i < n; i++) cin >> nums[i];
sort(nums.begin(), nums.end());
vector<i64> vec;
i64 sum = 0;
int L = 0, R = n - 1;
i64 ans = 0;
while (vec.size() < n) {
i64 v = nums[R];
if ((sum + v) / x > sum / x) {
vec.push_back(v);
sum += v;
ans += v;
R--;
}
else {
vec.push_back(nums[L]);
sum += nums[L];
L++;
}
}
cout << ans << endl;
for (int i = 0; i < n; i++) cout << vec[i] << " ";
cout << endl;
}
D. Locked Out
【题目】
给定长为n的数组nums,1<=nums[i]<=n
一次操作中 可以移除任意一个元素。
要求最终数组中 不存在任意的i<j,且nums[j]-nums[i]=1
也就是不能存在任何一个元素比它前面出现的某个元素恰好大1
[1,2,3,4,5]可以删减为[1,3,5]
[5,4,3,2,1]不需要删减
【题解1】
一道很好的题目。有很多技巧可以学。
不限制复杂度 能有什么思路?
差值为1启发我们按值排序dp
每个值可能有多个,但是总数是n
trick:按值分类,哈希套列表的形式是避免复杂度上升的处理办法。
此处由于值域固定<=n 所以可以直接列表套列表
trick:另一种可以把值和下标合并处理,形成一个单列表,每个元素为二元值。
trick:题目询问最少移除多少个,要能立即意识可以转换为最多保留多少个
也就是正难则反,并且最多保留个数非常类似于上升子序列之类的经典思想。
dp[x][i]定义为从小到大枚举到x的第i个下标,并选择x时【最多】可以保留的元素个数
现在考虑转移来源:
假设枚举到某个值x,由于是列表,假设枚举到的这个x位于原数组位置p。
那么dp[x]i可能从哪里转移
- 可能是dp[y] (y <= x - 2)的任意位置,显然我们需要取最大值。
因为只限制了不允许x - y = 1,这个来源是显然的。比如[1,3]这种 - 可能是dp[y][j] (y = x - 1, 这里j表示第j个y,其下标设为q,则必须有q > p)
也就是虽然y = x - 1 但是y位于x后面。比如[3,2]这种形式 - 可能是dp[x][j] (且这个x(第j个x)的下标q > p)
比较难理解的点,显然一个靠后的x对应的个数已知,则更靠前的x必然合法
比如[1,2,1,2],如果选择了第二个2,则必然前方不存在1,故前面的2自然可行。
反之不能成立。
这同时也启发我们枚举同一个值的不同位置时 要【倒序】枚举。
也就是dp[x][i]第一维的x从1-n枚举,对于每个x,其下标列表要是倒序的。
对于来源1,朴素的做法,可以用个数组mx,mx[i]表示枚举到值i时的最大值
这样取值和设值都比较方便。
对于来源2,抽象一下模型,每轮需要设置y在某个下标q下的值
而查询时,需要找到所有[p,n]范围内的最大值。显然这是个树状数组或者线段树模型
注意可以不单独查询y=x-1的值的下标,实际上可以直接查询所有值的[q,n]下标的最大值即可。
因为跟来源1或者3是重合的。
如果不用线段树,即在y=x-1的所有y下标中查找>p的那部分的最大值。
考虑到我们本来就是从大到小枚举的,实际这个值是一个【前缀最大值】
因此可以在维护时记录premax,然后用双指针或者二分去找到对应的值。
对于来源3,可以简单记录当前相同值的一系列下标下最大值即可
trick:如果来源1的值只用一个值表示呢?有个延迟更新的技巧
设mx表示全局最大值,premx表示前缀最大值
则每一轮可以这样,伪代码如下
假设从1开始枚举,此时枚举到4,mx存储的是到2的最大值,premx存储到3的最大值。
int tmp = premx; //先取出之前最大值(到2的) 用于计算,后续只使用tmp
premx = max(premx, curmx); //之后直接把最大值用上一轮的当前最大更新(即之前最大值变成了到3的)
curmx = max(curmx, XXXX); //更新当前最大值(变成到4的)
下一轮枚举到5,显然有存储最大值为3
【代码】
这里使用了二分查找 而不是线段树
#include <bits/stdc++.h>
using namespace std;
void solve() {
int n; cin >> n;
vector<vector<int>> all(n + 1);
for (int i = 0; i < n; i++) {
int v; cin >> v;
all[v].push_back(i);
}
for(int v = 1; v <= n; v++) sort(all[v].begin(), all[v].end(), greater<int>());
int preMax = 0;
int curMax = 0;
vector<vector<int>> pre(n + 1);
for (int v = 1; v <= n; v++) {
pre[v].resize(all[v].size());
int tmp = preMax;
preMax = max(preMax, curMax);
for (int i = 0; i < all[v].size(); i++) {
int p = all[v][i];
int leftMx = 0;
if (all[v - 1].size() > 0) {
int low = 0, high = all[v - 1].size() - 1;
while (low <= high) {
int mid = low + (high - low) / 2;
if (all[v - 1][mid] < p) high = mid - 1;
else low = mid + 1;
}
if (high >= 0) pre[v][i] = pre[v - 1][high] + 1;
}
if (i == 0) pre[v][i] = max(pre[v][i], tmp + 1);
else {
pre[v][i] = max({ pre[v][i], tmp + 1, pre[v][i - 1] + 1 });
}
curMax = max(curMax, pre[v][i]);
}
}
cout << n - curMax << endl;
}
int main() {
ios::sync_with_stdio(false);
int t; cin >> t;
while (t-- > 0) solve();
}
【题解2】
先介绍个东西叫做slope trick
而本题有一个非常类似slope trick思想的【贪心】做法
【代码】
void solve() {
int n; cin >> n;
vector<vector<int>> all(n + 1);
for (int i = 0; i < n; i++) {
int v; cin >> v;
all[v].push_back(i);
}
int ans = 0;
for (int i = 1; i < n; i++) {
if (all[i].size() == 0 || all[i + 1].size() == 0) continue;
int L = all[i].size() - 1;
int R = all[i + 1].size() - 1;
while (L >= 0 && R >= 0) {
if (all[i][L] < all[i + 1][R]) {
ans++;
all[i + 1].pop_back();
L--;
R--;
}
else L--;
}
}
cout << ans << endl;
}
wtf?
浙公网安备 33010602011771号