这几天好不容易想到五一假期能够有时间多赶下学习进度,结果婚礼的一堆事儿又忙的焦头烂额,计划总是赶不上变化,一步一步慢慢来吧。
Topic 1:排序子序列(模拟 + 贪心)
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n; cin >> n;
vector<int> A(n);
for(int i = 0; i < n; i++) cin >> A[i];
int count = 1; // 至少一段
int trend = 0; // 0: 未定, 1: 递增, -1: 递减
for(int i = 1; i < n; i++) //从1开始,0被视作第1段
{
if(A[i] > A[i - 1]) // 如果后一个>前一个
{
if(trend == -1) //之前的趋势如果是递减的
{
count++;//要从新记一段了
trend = 0;//趋势变为均势
}
else //之前的趋势是均势或者递增的
{
trend = 1;//维持在这个组内,维持趋势为递增即可
}
}
else if(A[i] < A[i - 1]) // 如果后一个<前一个
{
if(trend == 1) //之前的趋势如果是递增的
{
count++;
trend = 0;
}
else
{
trend = -1;
}
}
// 如果相等,trend 保持不变
}
cout << count << endl;
return 0;
}
比较简单的归纳,遇到了变化点就判断和上一次趋势的比较,如果趋势为均势or相同的趋势,归纳到一堆,否则就另起炉灶;
Topic 2:消减整数(bfs, 归纳法优化)
用BFS能做,但是用贪心和归纳更快
两条行动路径,我要找最短的肯定优先选2a,但是2a又需要满足最后结果恰好为0,那么也就是
(y-(n1*2a)- (n2*2*2a)-(n3*2*2*2a)- …)=0,这无数个2a组成了结果,这一坨n都可以简化为一个大N
=> y-N*2a= 0, 也就是y减N个2a恰好等于0
=> y % 2a = 0,那么当y % 2a等于0时,最后结果就能恰好为0,只是操作了N次而已
即y满足y%2a=0 时,优选选择-2a操作
#include <bits/stdc++.h>
using namespace std;
int minSteps(int &h)
{
int res = 0, a = 1;
while(h)
{
h -= a;
res++;
if(h % (a * 2) == 0) a *= 2;
}
return res;
}
int main()
{
int t, h;
cin >> t;
while (t--)
{
cin >> h;
cout << minSteps(h) << endl;
}
return 0;
}
Topic 3:最长上升子序列(二)(dp 贪心 二分)
对应LeetCode:300.最长递增子序列
class Solution {
public:
int LIS(vector<int>& a)
{
int n = a.size();
if (n == 0) return 0;
vector<int> dp(n, 1);// dp[i]:下标为i时最长子序列的长度 dp[i] = max(dp[i], dp[j] + 1) dp[j]:0 - j 之间最长严格上升序列的长度 j < i
int res = 1;
for (int i = 1; i < n; ++i)// 从下标1开始,dp[0]之前被初始化为1
{
for (int j = 0; j < i; ++j)
{
if (a[i] > a[j]) dp[i] = max(dp[i], dp[j] + 1);
}
res = max(res, dp[i]);
}
return res;
}
};
最初的样子,但是会超时,所以要用贪心加二分来优化
我们维护一个数组 dp
:
完成的dp表示最长的上升子序列
dp[i]表示:长度为i + 1的上升子序列的最小结尾元素,也可以把dp[0]特殊处理让下标对应
子序列要严格上升,所以结尾元素越小,越容易接上更大的数,形成更长的序列;所以我们希望:在所有可能长度的上升子序列中,结尾尽量小。
比如14和17,此时就取4
因为长度相同的情况下4后还能接567,而7只能接>7的
那么以14756举例,我创建一个dp容器,然后开始遍历原数组a{1,4,7,5,6},如果遍历的元素比dp中储存的最后一个元素大,意味着可以插入这个dp,构成最长的上升子序列
1 插入;4 插入;7 插入; 5,5这里要处理了,如果比7大的话,正常插入即可,但是5比7小,此时把5作为dp[2]的值,因为相比于1 4 7 而言 1 4 5同样的长度,但结尾数字更小,然后继续遍历,6 插入;最后dp中存着 1 4 5 6,此时dp的size()就是最终最大的上升子序列元素个数了;
class Solution {
public:
int LIS(vector<int>& a)
{
vector<int> dp;
// dp[i]: 长度为 i+1 的上升子序列的最小结尾元素。
// 这个数组不一定是实际的子序列,它是用来辅助计算长度的。
for (const auto& e : a) // 遍历输入数组a的每个元素e
{
// 在dp中二分查找第一个 >= e 的位置
// 目的是:找到e在dp中可以接在谁后面,或替换谁
auto it = lower_bound(dp.begin(), dp.end(), e);
// 如果e比dp中所有元素都大,说明e可以扩展出一个更长的子序列
if (it == dp.end()) dp.push_back(e);
// 否则,用e替换掉第一个 >= e 的位置,保持序列结尾尽可能小
else *it = e;
}
return dp.size(); // dp的长度就是最长上升子序列的长度
}
};
代码的重点在于lower_bound:
auto it = lower_bound(begin, end, x); —— 在一个有序数组(非降序)中,找到第一个“大于等于给定值 x
”的元素位置。
也可以自己手写:
int my_lower_bound(const vector<int>& a, int x)
{
int left = 0, right = a.size(); // 注意:right 是 a.size(),不是 a.size() - 1
while (left < right)
{
int mid = left + (right - left) / 2;
if (a[mid] < x)
{
left = mid + 1;
}
else
{
right = mid;
}
}
return left; // 返回的是第一个 >= x 的下标(可能是 a.size(),表示没有)
}