准备
阶段一
1.数据结构
2.基础算法:
2.1排序
最佳解决:
#include <bits/stdc++.h>
using namespace std;
#define N 100002
int n;
vector<pair<int, int>> dui; // 使用 pair 来存储使用时间和原始编号
int main() {
cin >> n;
dui.resize(n + 1);
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
dui[i] = {x, i};
}
// 按照使用时间从小到大排序,如果使用时间相同,则按原始编号从小到大排序
sort(dui.begin() + 1, dui.end(), [](const pair<int, int>& a, const pair<int, int>& b) {
if (a.first == b.first) {
return a.second < b.second;
}
return a.first < b.first;
});
for (int k = 1; k <= n; k++) {
cout << dui[k].second << " ";
}
return 0;
}
2.1.1快速排序
n个整数,请按照从小到大的顺序排序。
输入格式:
第一行数字n,1<=n<=100000
第二行n个整数,以一个空格分隔
输出格式:
从小到大排序后的数字,以一个空格分隔
输入样例:
5
4 2 1 3 5
输出样例:
1 2 3 4 5
思路
quicksort(int l,int r){
if(l>=r) return ; //终止条件
int p=jizhun(l,r); //一定基准,两quick递归
quicksort(l,p-1);
quicksort(p+1,r);
}
代码
#include<bits/stdc++.h>
using namespace std;
int n;
vector<int> a;
int ji(int l,int r){
int i=l;
int j=r+1;
while(i<j){
while(a[++i]<a[l]&&i<=r);
while(a[--j]>a[l]);
if(i>=j) break;
swap(a[j],a[i]);
}
swap(a[l],a[j]);
return j;
}
void quick(int l,int r){
if(l>=r) return ;
int id=ji(l,r);
quick(l,id-1);
quick(id+1,r);
}
int main(){
cin>>n;
a.resize(n);
for(int i=0;i<n;i++) cin>>a[i];
quick(0,n-1);
for(int i=0;i<n;i++) cout<<a[i]<<" ";
return 0;
}
2.1.2归并排序
已知有两个等长的非降序序列S1, S2, 设计函数求S1与S2并集的中位数。有序序列A0,A1,⋯,AN−1的中位数指A(N−1)/2的值,即第⌊(N+1)/2⌋个数(A0为第1个数)。
输入格式:
输入分三行。第一行给出序列的公共长度N(0<N≤100000),随后每行输入一个序列的信息,即N个非降序排列的整数。数字用空格间隔。
输出格式:
在一行中输出两个输入序列的并集序列的中位数。
输入样例1:
5
1 3 5 7 9
2 3 4 5 6
输出样例1:
4
输入样例2:
6
-100 -10 1 1 1 1
-50 0 2 3 4 5
输出样例2:
1
思路
注意!!!--->这道题合并集合,不做删除,所以合并后数组大小为2n
代码
#include<bits/stdc++.h>
using namespace std;
int n;
vector<int> a;
vector<int> b;
void merge(vector<int>& a,vector<int>& b,int n){
int i=0,j=0,k=1;
vector<int> c(3*n);
while(i<n&&j<n){
if(a[i]<=b[j]){
c[k]=a[i];
k++;
i++;
}
else if(a[i]>b[j]){
c[k]=b[j];
k++;
j++;
}
}
while(i<n){
c[k]=a[i];
i++;
k++;
}
while(j<n){
c[k]=b[j];
j++;
k++;
}
k--;
if(k%2!=0) cout<<c[k/2];
else cout<<(c[k/2]+c[k/2+1])/2;
}
int main(){
cin>>n;
a.resize(n);
b.resize(n);
for(int i=0;i<n;i++) cin>>a[i];
for(int j=0;j<n;j++) cin>>b[j];
merge(a,b,n);
return 0;
}
2.1.3桶排序
代码
#include <bits/stdc++.h>
using namespace std;
int n;
void buff(vector<int>& a){
if(a.empty()) return;
//找最大最小,确认桶数量(由数据范围定)
int mmax=0;
int mmin=500005;
for(int i=0;i<n;i++){
mmax=mmax>a[i]?mmax:a[i];
mmin=mmin<a[i]?mmin:a[i];
}
int num=mmax-mmin+1;
vector<vector<int>> tong(num);
//分配
for(int j=0;j<n;j++){
int id=a[j]-mmin;
tong[id].push_back(a[j]);
}
//排序
for(int k=0;k<num;k++){
sort(tong[k].begin(),tong[k].end());
}
//合并
int index = 0;
for (int o=0;o<num;o++) {
for (int x : tong[o]) {
a[index++] =x;
}
}
}
int main()
{
cin>>n;
vector<int> arr;
arr.resize(n);
for(int i=0;i<n;i++){
cin>>arr[i];
}
buff(arr);
for(int j=0;j<n;j++) cout<<arr[j]<<" ";
return 0;
}
2.1.4总结
1. 快速排序(Quick Sort)
快速排序通过选择一个基准元素,将数组分为两部分,然后递归地对这两部分进行排序。
定基准,两quick;
quick(int left,int right){
if(l>=r) return; //结束条件
int p=ji(int l,int r);
quick(l,p);
quick(p+1,r);
}
定基准:
int ji(int l,int r){
int i=l;
int j=r+1;
while(i<j){
while(a[++i]<a[l]&&i<=r);
while(a[--j]>a[l]);
if(i>=j) break;
swap(a[j],a[i]);
}
swap(a[l],a[j]);
return j;
}
2.归并排序
归并排序通过将数组分成两半,分别排序,然后将两个有序数组合并。
mid对半,两个sort+merge
void merge(vector<int>& arr, int left, int mid, int right) {
vector<int> temp(right - left + 1);
int i = left, j = mid + 1, k = 0;
while (i <= mid && j <= right) {
if (arr[i] <= arr[j]) {
temp[k++] = arr[i++];
} else {
temp[k++] = arr[j++];
}
}
while (i <= mid) temp[k++] = arr[i++];
while (j <= right) temp[k++] = arr[j++];
for (int p = 0; p < k; p++) {
arr[left + p] = temp[p];
}
}
void mergeSort(vector<int>& arr, int left, int right) {
if (left >= right) return; // 递归终止条件
int mid = (left + right) / 2;
mergeSort(arr, left, mid); // 排序左半部分
mergeSort(arr, mid + 1, right); // 排序右半部分
merge(arr, left, mid, right); // 合并两部分
}
3. 桶排序(Bucket Sort)
桶排序将元素分配到多个桶中,每个桶单独排序,最后合并所有桶。
void bucketSort(vector<float>& arr) {
vector<vector<float>> buckets(num); // 创建n个桶
桶的分配:
-
我们根据数组中的最大值和最小值确定桶的数量。
-
每个桶的索引通过
num - minVal
计算得到,确保每个元素都能正确分配到桶中
// 将元素分配到桶中
for (float num : arr) {
int bucketIndex = num-minVal;
buckets[bucketIndex].push_back(num);
}
// 对每个桶进行排序
for (auto& bucket : buckets) {
sort(bucket.begin(), bucket.end());
}
// 合并所有桶
int index = 0;
for (auto& bucket : buckets) {
for (float num : bucket) {
arr[index++] = num;
}
}
}
2.2二分查找
代码:
#include <bits/stdc++.h>
using namespace std;
int n,k;
int maxs=0;
vector<pair<int,int>> cady;
bool check(int mid){
long long sum = 0;
for (int i = 0; i < n; i++) {
int h = cady[i].first;
int w = cady[i].second;
sum += (long long)(h / mid) * (w / mid); // 计算可以切出多少个边长为 mid 的正方形
if (sum >= k) {
return true;
}
}
return false;
}
int find(int l,int r){
int result = 0;
while (l <= r) {
int mid = (l+r)/2;
if (check(mid)) {
result = mid;
l = mid + 1;
} else {
r = mid - 1;
}
}
return result;
}
int main()
{
cin>>n>>k;
cady.resize(n);
for(int i=0;i<n;i++){
cin>>cady[i].first>>cady[i].second;
int s=min(cady[i].first,cady[i].second);
maxs=maxs>s?maxs:s;
}
cout<<find(1,maxs);
return 0;
}
2.3.BFS/DFS
2.3.1BFS
BFS介绍:
BFS通常使用队列(Queue)数据结构来实现。队列是一种先进先出(FIFO)的数据结构,非常适合用于逐层扩展搜索范围。具体实现步骤如下:
-
创建一个队列,并将起始节点入队。
-
标记起始节点为已访问。
-
当队列不为空时,执行以下步骤:
- 从队列中取出一个节点,作为当前节点。
- 遍历当前节点的所有邻居节点。
- 如果某个邻居节点尚未被访问,则将其加入队列,并标记为已访问。
-
应用场景
BFS因其独特的遍历方式,在计算机科学中得到了广泛的应用,包括但不限于以下几个方面:
-
最短路径问题:在无权图中,BFS可以用于找到从起点到终点的最短路径。例如,在迷宫问题中,BFS可以帮助找到从入口到出口的最短路径。
-
连通性问题:BFS可以用于判断图中的两个节点是否连通。通过从任意节点开始进行BFS遍历,如果能够访问到目标节点,则说明两个节点是连通的;否则,它们是不连通的。
-
拓扑排序问题:BFS可以用于对有向无环图(DAG)进行拓扑排序。拓扑排序是一种对有向图进行线性排序的方法,使得对于每一条有向边 (u, v),顶点 u 在排序结果中都出现在顶点 v 之前。
-
图的遍历:BFS可以用于遍历图中的所有节点,确保每个节点都被访问一次。
1.走迷宫(BFS)
代码:
2.七步诗
代码:
#include <bits/stdc++.h>
using namespace std;
const int dx[8] = {1, 2, 2, 1, -1, -2, -2, -1};
const int dy[8] = {2, 1, -1, -2, -2, -1, 1, 2};
int main() {
int n, m;
cin >> n >> m;
vector<vector<char>> grid(n, vector<char>(m));
pair<int, int> start;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
cin >> grid[i][j];
if (grid[i][j] == 'S') {
start = {i, j};
}
}
}
queue<pair<int, int>> q;
q.push(start);
vector<vector<bool>> visited(n, vector<bool>(m, false));
visited[start.first][start.second] = true;
int maxBeans = 0;
while (!q.empty()) {
auto current = q.front();
q.pop();
int x = current.first;
int y = current.second;
if (grid[x][y] == 'b') {
maxBeans++;
}
for (int i = 0; i < 8; ++i) {
int nx = x + dx[i];
int ny = y + dy[i];
if (nx >= 0 && nx < n && ny >= 0 && ny < m && !visited[nx][ny] && grid[nx][ny] != 'x') {
visited[nx][ny] = true;
q.push({nx, ny});
}
}
}
cout << maxBeans << endl;
return 0;
}
2.3.2.DFS
1.排列问题
解释:
代码的逻辑问题
-
a[x]++
的作用:-
统计每个数字
x
出现的次数。 -
但代码中
a[x]
的值可能为 0(如果x
没有出现过),这会导致后续的累乘结果为 0。
-
-
res *= a[i]
的作用:-
如果某个
a[i]
为 0,则res
会变为 0,且后续的res
也会保持为 0。 -
这意味着一旦某个数字
i
没有出现过,res
就会变为 0,且ans
不会再增加。
-
-
ans += res
的作用:-
将当前的
res
累加到ans
上。 -
由于
res
可能会变为 0,ans
的值会在某个点停止增长。
-
代码的实际功能
这段代码的目的是计算某种排列的数量,具体逻辑如下:
-
统计每个数字
x
在[1, n]
范围内出现的次数。 -
通过累乘
a[i]
的值,计算某种排列的数量。 -
如果某个数字
i
没有出现过(即a[i] == 0
),则排列数量为 0。
2.简单数组操作
代码:
#include <bits/stdc++.h>
using namespace std;
int n, a, b;
vector<int> gra;
int mmin = 1e9;
void dfs(int t) {
if (t >= mmin) return; // 剪枝
bool flag = true;
for (int i = 1; i <= n; i++) {
if (gra[i] >= 0) {
flag = false;
break;
}
}
if (flag) {
mmin = min(mmin, t);
return;
}
for (int i = 2; i <= n - 1; i++) {
if (gra[i - 1] >= 0 || gra[i] >= 0 || gra[i + 1] >= 0) {
gra[i - 1] -= b, gra[i] -= a, gra[i + 1] -= b;
dfs(t + 1);
gra[i - 1] += b, gra[i] += a, gra[i + 1] += b;
}
}
}
int main() {
cin >> n >> a >> b;
gra.resize(n + 1);
for (int i = 1; i <= n; i++) cin >> gra[i];
int sum = 0;
while (gra[1] >= 0) {
gra[1] -= b, gra[2] -= a, gra[3] -= b;
sum++;
}
while (gra[n] >= 0) {
gra[n] -= b, gra[n - 1] -= a, gra[n - 2] -= b;
sum++;
}
dfs(sum);
cout << mmin;
return 0;
}
3.动态规划
3.1背包问题
3.1.1 0-1背包
(每个物品只使用一次)
动态方程: dp[i][j]=max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]); //选
dp[i][j]=dp[i-1][j]; //不选
3.1.1.1 小背包
代码:
#include <bits/stdc++.h>
using namespace std;
#define N 1001
int n, m;
int w[N], v[N];
int dp[N][N] = {0};
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> v[i] >> w[i];
}
for (int j = 1; j <= n; j++) {
for (int k = 0; k <= m; k++) {
if (v[j] <= k) {
dp[j][k] = max(dp[j-1][k], dp[j-1][k-v[j]] + w[j]);
} else {
dp[j][k] = dp[j-1][k];
}
}
}
cout << dp[n][m];
return 0;
}
解析:
这是动态规划表,列标签有点错位,自己脑部挪一下。
k=0 | k=1 | k=2 | k=3 | k=4 | k=5 | |
---|---|---|---|---|---|---|
j=0 | 0 | 0 | 0 | 0 | 0 | 0 |
j=1 | 0 | 2 | 2 | 2 | 2 | 2 |
j=2 | 0 | 2 | 4 | 6 | 6 | 6 |
j=3 | 0 | 2 | 4 | 6 | 6 | 8 |
j=4 | 0 | 2 | 4 | 6 | 6 | 8 |
简单解析一下:初始化全部为0,0件物品是所有价值为0。
装入第一件物品,当未达到足够容量时,等于同容量装入前一件的价值,如果能装时--》考虑:
装:未装入该物品前的价值+装入物品的价值。
不装:及同容量上一件物品的价值。
3.1.2 完全背包
某个物品可以使用无限次;
动态方程:
dp[j][k]=max(dp[j-1][k],value[j]+dp[j][k-weight[j]]); //可以放
dp[j][k]=dp[j-1][k]; //不可以放
3.1.2.1 大背包
代码:
#include <bits/stdc++.h>
using namespace std;
#define N 1002
int n,m;
int dp[N][N]={0};
int value[N],weight[N];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>weight[i]>>value[i];
}
for(int j=1;j<=n;j++){
for(int k=0;k<=m;k++){
if(weight[j]<=k) dp[j][k]=max(dp[j-1][k],value[j]+dp[j][k-weight[j]]);
else dp[j][k]=dp[j-1][k];
}
}
cout<<dp[n][m];
return 0;
}
3.1.3 未知背包
3.1.3.1 购物策略
代码:
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
ll dp[4010];
ll c[2010],t[2010];
int main()
{
int n;cin>>n;
ll v =0;
for(int i=1;i<=n;i++)
{
cin>>t[i]>>c[i];
t[i]++;
v=max(v,t[i]);
}
v+=n;
for(int i=1;i<=v;i++) dp[i]=1e18;
for(int i=1;i<=n;i++)
{
for(int j=v;j>=t[i];j--)
{
dp[j]=min(dp[j],dp[j-t[i]]+c[i]);
}
}
ll ans = 1e18;
for(int i=n;i<=v;i++)
{
ans=min(ans,dp[i]);
}
cout<<ans<<endl;
return 0;
3.1.3.2
4.贪心
4.1 滑动窗口
代码:
#include<cstdio>
#include<algorithm>
using namespace std;
int n,d,k;
int nowlike[100005];
struct node{
int ts;
int id;
};
node arr[100005];
bool ishot[100005];
bool cmp(node x,node y)
{
return x.ts<y.ts;
}
int main()
{
scanf("%d%d%d",&n,&d,&k);
for(int i=1;i<=n;i++)
scanf("%d%d",&arr[i].ts,&arr[i].id);
sort(arr+1,arr+1+n,cmp);
int l = 1;
for(int i=1;i<=n;i++)
{
nowlike[arr[i].id]++;
while(arr[i].ts >= arr[l].ts + d) nowlike[arr[l++].id]--; // 移动窗口左边界,确保窗口内的时间跨度不超过d
if(nowlike[arr[i].id]>=k) ishot[arr[i].id] = true;
}
for(int i=0;i<=100005;i++)
if(ishot[i])
printf("%d\n",i);
return 0;
}