【AcWing】基础算法

目录

1、快速排序

1.1 快速排序

1.2 第k个数

2、归并排序

2.1 归并排序

2.2 逆序对的数量

3、二分

3.1 数的范围

3.2 数的三次方根

4、高精度

4.1 高精度加法

4.2 高精度减法

4.3 高精度乘法

4.4 高精度除法

5、前缀和与差分

5.1 前缀和

5.2 子矩阵的和

5.3 差分

5.4 差分矩阵

6、双指针算法

6.1 最长连续不重复的子序列

6.2 数组元素的目标和

6.3 判断子序列

7、位运算

8、离散化

9、区间合并


1、快速排序

1.1 快速排序

785. 快速排序 - AcWing题库

1. 确定分界点:q[l]、q[r]、q[l + r >> 2]、随机

2. 调整区间

我们要将区间调整为下面这样子

当q[i] < x时,i++,当q[i] >= x时,i停止。当q[j] > x时,j--,当q[j] <= x时,j停止。判断一下此时是否i < j,若i < j则交换,否则就出循环。

此时为什么是正确的呢?

因为在任意时刻,i左边区间内的数都是小于等于x的,j右边区间内的数都是大于等于x的

3. 递归处理左右两段

#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int q[N], n;void quick_sort(int l, int r)
{if (l >= r) return ;int i = l - 1, j = r + 1, x = q[l];while (i < j){while (q[ ++ i] < x)    ;while (q[ -- j] > x)    ;if (i < j)  swap(q[i], q[j]);}quick_sort(l, j);quick_sort(j + 1, r);
}int main()
{scanf("%d", &n);for (int i = 0; i < n; i ++ )   scanf("%d", &q[i]);quick_sort(0, n - 1);for (int i = 0; i < n; i ++ )   printf("%d ", q[i]);return 0;
}

讲解代码中几个需要注意的地方:

1. 为什么while(i < j)里面的两个while循环不需要先判断i < j?

i < j是为了防止越界访问。我们先来看结束时,i和j的分布情况

(1) i和j可能会相遇在某一个x,此时i == j

(2) 因为i左边的数是<=x,j右边的数是>=x,所以i一定会停在j右边的区间的第一个数,j一定会停在i左边的区间的第一个数。最先开始的时候,i的左边没有数,j的右边没有数,会不会出现意外呢?不会的,以此时选取x=q[l]为例,一开始q[i] == x,i是不会动的,j会先动。若q[j] <= x,那么交换完q[i]和q[j]后,i++,j--,i的左边有值,j的右边有值。若q[j] > x,则j--找<=x的元素,找到后,两者一交换,左右就都不为空了。

2. 为什么一上来直接i++、j--?

因为无论是正常走,还是交换完,都需要让i++、j--。

注意:初始化时i = l - 1,j = r + 1

3. 递归时的区间选择

上面我们选取的分界点是x = q[l],递归时就是使用j,若选取的分界点是x = q[r],递归时就是使用i,否则就会出现死循环

#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int q[N], n;void quick_sort(int l, int r)
{if (l >= r) return ;int i = l - 1, j = r + 1, x = q[r];while (i < j){while (q[ ++ i] < x)    ;while (q[ -- j] > x)    ;if (i < j)  swap(q[i], q[j]);}quick_sort(l, i - 1);quick_sort(i, r);
}int main()
{scanf("%d", &n);for (int i = 0; i < n; i ++ )   scanf("%d", &q[i]);quick_sort(0, n - 1);for (int i = 0; i < n; i ++ )   printf("%d ", q[i]);return 0;
}

所以当选取的分界点是q[l]或q[r]时,要注意迭代区间的更新

当然,选取分界点时也可以直接使用随机数法或区中间值的方法

#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int q[N], n;void quick_sort(int l, int r)
{if (l >= r) return ;swap(q[l], q[l + rand() % (r - l + 1)]);int i = l - 1, j = r + 1, x = q[l];while (i < j){while (q[ ++ i] < x)    ;while (q[ -- j] > x)    ;if (i < j)  swap(q[i], q[j]);}quick_sort(l, j);quick_sort(j + 1, r);//quick_sort(l, i - 1);//quick_sort(i, r); 也行
}int main()
{scanf("%d", &n);for (int i = 0; i < n; i ++ )   scanf("%d", &q[i]);quick_sort(0, n - 1);for (int i = 0; i < n; i ++ )   printf("%d ", q[i]);return 0;
}

此时会有疑问,就是为什么此时分界点选择的是q[l],但是递归的时候可以用i呢?

实际上也是不行的,比如像5 4 1 3 2,若rand() == 2,变为1 4 5 3 2,若用i仍然会死循环,可以是因为rand()不一定是2 

这个算法相比较我们之前的快速排序算法是有优势的

void QuickSort(int* a, int left, int right)//Hoare法
{if (left >= right)return;int begin = left, end = right;//随机选keyint randi = rand() % (right - left + 1);randi += begin;Swap(&a[left], &a[randi]);int keyi = left;while (begin < end){while(begin < end && a[end] >= a[keyi])end--;while(begin < end && a[begin] <= a[keyi])begin++;Swap(&a[begin], &a[end]);}Swap(&a[begin], &a[keyi]);QuickSort(a, left, begin - 1);QuickSort(a, begin + 1, right);
}

我们之前的算法在遇到全相等的情况时,时间复杂度是O(N^2),因为遇到==基准值时是会跳过去的,而现在的代码不会

1.2 第k个数

786. 第k个数 - AcWing题库

可以直接使用快速排序先将数组排序,然后再输出第k个数,时间复杂度是O(N*logN)

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10;
int q[N],n,k;
void quick_sort(int l, int r)
{if(l >= r) return;swap(q[l], q[l + rand() % (r - l + 1)]);int i = l - 1,j = r + 1,x = q[l];while(i < j){while(q[++i] < x) ;while(q[--j] > x) ;if(i < j) swap(q[i], q[j]);}quick_sort(l, j);quick_sort(j + 1, r);
}
int main()
{scanf("%d%d", &n, &k);for(int i = 0;i < n;i++) scanf("%d", &q[i]);quick_sort(0, n - 1);printf("%d\n", q[k - 1]);return 0;
}

也可以使用快速选择算法

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10;
int q[N],n,k;
int quick_sort(int l, int r,int k)
{if(l >= r) return q[l];int i = l - 1,j = r + 1,x = q[l];while(i < j){while(q[++i] < x) ;while(q[--j] > x) ;if(i < j) swap(q[i], q[j]);}int sl = j - l + 1;if(sl >= k) return quick_sort(l, j, k);else return quick_sort(j + 1, r, k - sl);
}
int main()
{scanf("%d%d", &n, &k);for(int i = 0;i < n;i++) scanf("%d", &q[i]);cout<<quick_sort(0, n - 1, k)<<endl;return 0;
}

这道题是保证第k个数在区间里面,所以区间里面至少有1个数,可以l==r作为递归结束条件,但是快速排序中一定要是l >= r,因为快速排序中l可能大于r 

2、归并排序

2.1 归并排序

787. 归并排序 - AcWing题库

1. 确定分界点    mid = (l + r) / 2

2. 递归排序左右区间

3. 归并 ---- 将两个有序数组合二为一

注意:在这个步骤中,为了保证排序的稳定性,当两指针指向的值相等时,要将第一个数组的值放入临时数组

#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int q[N], tmp[N], n;
void merge_sort(int l,int r)
{if(l >= r) return;int mid = (l + r) / 2;merge_sort(l, mid);merge_sort(mid + 1, r);int i = l,j = mid + 1,k = 0;while(i <= mid && j <= r){if(q[i] <= q[j]) tmp[k++] = q[i++];else tmp[k++] = q[j++];}while(i <= mid) tmp[k++] = q[i++];while(j <= r) tmp[k++] = q[j++];for(i = l,j = 0;i <= r;i++,j++) q[i] = tmp[j];
}
int main()
{scanf("%d", &n);for(int i = 0;i < n;i++) scanf("%d", &q[i]);merge_sort(0, n - 1);for(int i = 0;i < n;i++) printf("%d ", q[i]);return 0;
}

2.2 逆序对的数量

788. 逆序对的数量 - AcWing题库

可以像归并排序时一样,将数组划分为两部分,此时逆序对会有3种情况

我们只需要一直向下递归,直到每个区间都只有一个值,就能都变成第三种情况

并且我们可以在归并的同时就行排序,因为当q[i] > q[j]时,则q[i]到q[mid]都是大于q[j]的

在"并"的过程中,利用两个有序数组计算逆序对的数量

此时q[i] > q[j],那么前半数组剩余的值都和q[j]构成逆序对

注意,逆序对的数量最多会大于int,所以开long long

#include<iostream>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
int q[N], tmp[N], n;
ll merge_sort(int q[], int l, int r)
{if(l >= r) return 0;int mid = l + r >> 1;ll res = merge_sort(q, l, mid) + merge_sort(q, mid + 1, r);int k = 0,i = l,j = mid + 1;while(i <= mid && j <= r){if(q[i] <= q[j]) tmp[k++] = q[i++];else{tmp[k++] = q[j++];res += mid - i + 1;}}while(i <= mid) tmp[k++] = q[i++];while(j <= r) tmp[k++] = q[j++];for(int i = l,j = 0;i <= r;i++,j++) q[i] = tmp[j];return res;
}
int main()
{scanf("%d", &n);for(int i = 0;i < n;i++) scanf("%d", &q[i]);ll res = merge_sort(q, 0, n - 1);printf("%lld\n", res);return 0;
}

3、二分

bool check(int x) {/* ... */} // 检查x是否满足某种性质// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r)
{while (l < r){int mid = l + r >> 1;if (check(mid)) r = mid;    // check()判断mid是否满足性质else l = mid + 1;}return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r)
{while (l < r){int mid = l + r + 1 >> 1;if (check(mid)) l = mid;else r = mid - 1;}return l;
}作者:yxc
来源:AcWing

模板不需要特意去记什么时候在计算中点时要+1,只需要写出check后,根据更新判断 

为什么计算中点时有时候要+1,有时候不需要呢?

因为l + r >> 1是向下取整,当l = r - 1时,若chack为真,则没变,会死循环

3.1 数的范围

789. 数的范围 - AcWing题库

这道题是整数二分,分为找左端点和右端点

#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N], n, q, k;
int main()
{scanf("%d %d", &n, &q);for(int i = 0;i < n;i++) scanf("%d", &a[i]);while(q--){scanf("%d", &k);// 先找左端点int l = 0, r = n - 1;while(l < r){int mid = l + r >> 1;if(a[mid] < k) l = mid + 1;else r = mid;}if(a[l] != k) printf("-1 -1\n");else{// 找右端点printf("%d ", l);l = 0, r = n - 1;while(l < r){int mid = l + r + 1 >> 1;if(a[mid] > k) r = mid - 1;else l = mid;}printf("%d\n", l);}}return 0;
}

3.2 数的三次方根

790. 数的三次方根 - AcWing题库

这道题是浮点数二分,初始时让l等于数的范围的左端点,r等于数的范围的右端点,一直二分,直到l和r之间的距离足够小即可。如何定义足够小呢?如果题目的浮点数保留6位小数,则while的循环条件时r - l > 1e-8

注意:浮点数二分不能直接取l=0,r=x,因为当0<x<1时,可能会出错,如求二次方跟时,x=0.01,而0.01的二次方根是0.1。在这道题中就不需要考虑这个,因为数的范围已经确定了

#include<iostream>
using namespace std;
int main()
{double n;scanf("%lf", &n);double l = -10000,r = 10000;while(r - l > 1e-8){double mid = (r + l) / 2;if(mid * mid * mid < n) l = mid;else r = mid;}printf("%lf\n", l);return 0;
}

浮点数二分相对于整数二分更加简单,因为没有+1-1,所以不需要考虑边界问题 

4、高精度

这里的高精度是指大数之间的加减乘除运算

4.1 高精度加法

791. 高精度加法 - AcWing题库

高精度加法,A + B,通常A和B是大小在0到10^6的整数

大整数的存储通常使用数组来存储,并且最好是倒过来存储,方便进位

使用数组进行加法的过程就是模拟整数加法,Ci = Ai + Bi + t,t表示进位

#include<iostream>
#include<string>
#include<vector>
using namespace std;
vector<int> add(vector<int> &A, vector<int> &B)
{vector<int> C;int t = 0; // 记录进位for(int i = 0;i < A.size() || i < B.size();i++){if(i < A.size()) t += A[i];if(i < B.size()) t += B[i];C.push_back(t % 10);t /= 10;}if(t) C.push_back(1);return C;
}
int main()
{string a, b;vector<int> A, B;cin>>a>>b;for(int i = a.size() - 1;i >= 0;i--) A.push_back(a[i] - '0');for(int i = b.size() - 1;i >= 0;i--) B.push_back(b[i] - '0');vector<int> C = add(A, B);for(int i = C.size() - 1;i >= 0;i--) printf("%d", C[i]);return 0;
}

4.2 高精度减法

792. 高精度减法 - AcWing题库

高精度减法,A - B,通常A和B是大小在0到10^6的整数

因为一道题中结果可能很大,会使用到高精度的加减乘除,所以四种运算的存储方式相同

若Ai - Bi - t大于等于0,Ci = Ai - Bi - t

若Ai - Bi - t小于0,Ci = Ai - Bi - t + 10,t表示借位

#include<iostream>
#include<string>
#include<vector>
using namespace std;
bool cmp(vector<int>& A, vector<int>& B)
{if(A.size() != B.size()) return A.size() > B.size();for(int i = A.size() - 1;i >= 0;i--)if(A[i] != B[i]) return A[i] > B[i];return true;
}
vector<int> sub(vector<int> &A, vector<int> &B)
{vector<int> C;int t = 0;for(int i = 0;i < A.size();i++){t = A[i] - t;if(i < B.size()) t-= B[i];C.push_back((t + 10) % 10);if(t < 0) t = 1;else t = 0;}while(C.size() > 1 && C.back() == 0) C.pop_back(); // 处理前导0return C;
}
int main()
{string a, b;vector<int> A, B;cin>>a>>b;for(int i = a.size() - 1;i >= 0;i--) A.push_back(a[i] - '0');for(int i = b.size() - 1;i >= 0;i--) B.push_back(b[i] - '0');if(cmp(A, B)) // A >= B{vector<int> C = sub(A, B);for(int i = C.size() - 1;i >= 0;i--) printf("%d", C[i]);}else{vector<int> C = sub(B, A);printf("-");for(int i = C.size() - 1;i >= 0;i--) printf("%d", C[i]);}return 0;
}

sub中是默认A大于等于B的

(t + 10) % 10,当t >= 0时结果是不变的,当t < 0时,这个位置需要借位

还需要处理前导0,如789-786,此时A是9 8 7,B是6 8 7,减完之后C是3 0 0,反转过后也就是003,我们需要的是3,所以需要处理前导0 

4.3 高精度乘法

793. 高精度乘法 - AcWing题库

高精度乘法是一个很大的数字乘一个较小的数字,len(A)<=10^6,b<=10000

我们这里是将b当成一个整体,与A的每一位相乘

Ci = (Ai * b + t) % 10,t表示进位

#include<iostream>
#include<vector>
#include<string>
using namespace std;
int b;
vector<int> mul(vector<int>& A, int b)
{int t = 0;vector<int> C;for(int i = 0;i < A.size() || t;i++) // 结束条件是A遍历完,并且t也为0{if(i < A.size()) t += A[i] * b;C.push_back(t % 10);t /= 10;}while(C.size() > 1 && C.back() == 0) C.pop_back(); // 处理前导0return C;
}
int main()
{vector<int> A;string a; cin>>a>>b;for(int i = a.size() - 1;i >= 0;i--) A.push_back(a[i] - '0');vector<int> C = mul(A, b);for(int i = C.size() - 1;i >= 0;i--) printf("%d", C[i]);return 0;
}

同样需要处理前导0,如A = [3, 2, 1],b = 0,结果是C = [0, 0, 0]

4.4 高精度除法

高精度除法通常A <= 10^6,b <= 10000

除法是从最高位开始计算的,而加减乘都是从最低位计算,为了保持统一,除法计算完reverse

#include<iostream>
#include<vector>
#include<string>
#include<algorithm>
using namespace std;
vector<int> div(vector<int>& A, int b,int& r)
{vector<int> C;r = 0;for(int i = A.size() - 1;i >= 0;i--){r = r * 10 + A[i];C.push_back(r / b);r %= b;}reverse(C.begin(), C.end());while(C.size() > 1 && C.back() == 0) C.pop_back(); // 处理前导0return C;
}
int main()
{string a;int b;cin>>a>>b;vector<int> A;for(int i = a.size() - 1;i >= 0;i--) A.push_back(a[i] - '0');int r; // 余数vector<int> C = div(A, b, r);for(int i = C.size() - 1;i >= 0;i--) printf("%d", C[i]);printf("\n");printf("%d\n", r);return 0;
}

 同样需要处理前导0,如1234 / 11,A = [4, 3, 2, 1],b = 11,计算完后C = [0, 1, 1, 2],逆置完后C = [2, 1, 1, 0]

5、前缀和与差分

5.1 前缀和

795. 前缀和 - AcWing题库

#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int q[N], dp[N], n, m, l, r;
int main()
{scanf("%d %d", &n, &m);for(int i = 1;i <= n;i++) {scanf("%d", &q[i]);dp[i] = dp[i - 1] + q[i];}while(m--){scanf("%d %d", &l, &r);printf("%d\n", dp[r] - dp[l - 1]);}return 0;
}

5.2 子矩阵的和

796. 子矩阵的和 - AcWing题库

#include<iostream>
using namespace std;
const int N = 1010;
int m, n, q, x1, y1, x2, y2;
int a[N][N], dp[N][N];
int main()
{scanf("%d%d%d", &n, &m, &q);for(int i = 1;i <= n;i++){for(int j = 1;j <= m;j++){scanf("%d", &a[i][j]);dp[i][j] = dp[i - 1][j] + dp[i][j - 1] - dp[i - 1][j - 1] + a[i][j];}}while(q--){scanf("%d%d%d%d", &x1, &y1, &x2, &y2);printf("%d\n", dp[x2][y2] - dp[x2][y1 - 1] - dp[x1 - 1][y2] + dp[x1 - 1][y1 - 1]);}return 0;
}

5.3 差分

797. 差分 - AcWing题库

差分就是前缀和的逆过程

给定一个原数组a,我们要求差分数组b,使得a数组是b数组的前缀和,即a[i]=b[1]+b[2]+...+b[i]

1. 获得差分数组

很容易就能想到b[i] = a[i] - a[i - 1]

2. 使用差分数组

我们要对给定的区间[l, r]内的每个a数组加上c,按照正常做法,编译a数组的区间[l, r],每个数加上c,时间复杂度是O(m * n)。

此时就可以使用差分数组了。因为a数组是b数组的前缀和,所以我们会发现对a数组的区间[l, r]每个数加上c,实际上只需要对b[l] += c,b[r + 1] -= c

注意,因为我们对b进行了操作,但没对a操作,所以此时a数组名义上是b数组的前缀和,但是数值上已经不是了,所以m次操作完成后,要重新求一遍a

#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N], b[N];
int m, n, l, r, c;
int main()
{scanf("%d %d", &n, &m);for(int i = 1;i <= n;i++) scanf("%d", &a[i]);for(int i = 1;i <= n;i++) b[i] = a[i] - a[i - 1];while(m--){scanf("%d %d %d", &l, &r, &c);b[l] += c;b[r + 1] -= c;}for(int i = 1;i <= n;i++){a[i] = a[i - 1] + b[i];printf("%d ", a[i]);}return 0;
}

我们会发现,两步是可以使用同一个函数的。第二步是在a数组的区间[l, r]上插入值c。我们可以把第一步抽象为,假设a数组全为0,那么b数组也全为0,现在需要往a数组的[i, i]区间插入数值a[i],因为a数组并不是真的全为0,所以此时a数组既是b数组名义上的前缀和,也是数值上的前缀和

#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N], b[N];
int m, n, l, r, c;
void insert(int l, int r, int c)
{b[l] += c;b[r + 1] -= c;
}
int main()
{scanf("%d %d", &n, &m);for(int i = 1;i <= n;i++) {scanf("%d", &a[i]);insert(i, i, a[i]);}while(m--){scanf("%d %d %d", &l, &r, &c);insert(l, r, c);}for(int i = 1;i <= n;i++){a[i] = a[i - 1] + b[i];printf("%d ", a[i]);}return 0;
}

5.4 差分矩阵

798. 差分矩阵 - AcWing题库

这个就是二维差分,与前面的差分是类似的

#include<iostream>
using namespace std;
const int N = 1010;
int n, m, q, x1, y1, x2, y2, c;
int a[N][N], b[N][N];
void insert(int x1,int y1,int x2,int y2,int c)
{b[x1][y1] += c;b[x2 + 1][y1] -= c;b[x1][y2 + 1] -= c;b[x2 + 1][y2 + 1] += c;
}
int main()
{scanf("%d %d %d", &n, &m, &q);for(int i = 1;i <= n;i++){for(int j = 1;j <= m;j++){scanf("%d", &a[i][j]);insert(i, j, i, j, a[i][j]);}}while(q--){scanf("%d %d %d %d %d", &x1, &y1, &x2, &y2, &c);insert(x1, y1, x2, y2, c);}for(int i = 1;i <= n;i++){for(int j = 1;j <= m;j++){a[i][j] = a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1] + b[i][j];printf("%d ", a[i][j]);}printf("\n");}return 0;
}

6、双指针算法

6.1 最长连续不重复的子序列

799. 最长连续不重复子序列 - AcWing题库

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10;
int n;
int a[N], b[N];
int main()
{scanf("%d", &n);for(int i = 0;i < n;i++) scanf("%d", &a[i]);int ans = 0;for(int i = 0,j = 0;i < n;i++){b[a[i]]++;while(b[a[i]] > 1){b[a[j]]--;j++;}ans = max(ans, i - j + 1);}printf("%d", ans);return 0;
}

6.2 数组元素的目标和

800. 数组元素的目标和 - AcWing题库 

#include<iostream>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
int n, m, x;
ll a[N], b[N];
int main()
{scanf("%d %d %d", &n, &m, &x);for(int i = 0;i < n;i++) scanf("%lld", &a[i]);for(int i = 0;i < m;i++) scanf("%lld", &b[i]);int i = 0,j = m - 1;while(i < n && j >= 0){long long sum = a[i] + b[j];if(sum < x) i++;else if(sum > x) j--;else {printf("%d %d\n", i, j);i++, j--;}}return 0;
}

6.3 判断子序列

2816. 判断子序列 - AcWing题库 

#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int n, m;
int a[N], b[N];
int main()
{scanf("%d %d", &n, &m);for(int i = 0;i < n;i++) scanf("%d", &a[i]);for(int i = 0;i < m;i++) scanf("%d", &b[i]);int j = 0;for(int i = 0;i < m;i++){if(j >= n) break;if(a[j] == b[i]) j++;}if(j == n) printf("Yes");else printf("No");return 0;
}

7、位运算

801. 二进制中1的个数 - AcWing题库

位运算中,主要是两种问题

1. n的二进制表示中第k位是几  n >> k & 1,例如n=15,二进制是1010,左边是高位,右边是低位,下标是3210

2. lowbit(x):返回x的最后一位1,例如x=15,二进制是1010,lowbit(x)=10

lowbit是如何求的呢?x&-x = x&(~x+1)

lowbit可用于统计x的二进制表示中1的个数,每次将x中的一位1减掉,知道x变成0

使用方法一解题:

#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int n, a[N], b[N];
int main()
{scanf("%d", &n);for(int i = 0;i < n;i++) scanf("%d", &a[i]);for(int i = 0;i < n;i++){int sum = 0;for(int k = 0;k <= 31;k++)if((a[i] >> k & 1) == 1) sum++;b[i] = sum;}for(int i = 0;i < n;i++) printf("%d ", b[i]);return 0;
}

使用方法二解题:

// 方法二
int lowbit(int x)
{return x & -x;
}
int main()
{int n;scanf("%d", &n);while(n--){int x;scanf("%d", &x);int res = 0;while(x) x -= lowbit(x), res++;cout<<res<<" ";}cout<<endl;return 0;
}

8、离散化

802. 区间和 - AcWing题库

这道题数轴上的数字是[-10^9, 10^9],一共是2*10^9个数,会进行n次插入和m次查询,每次查询会输入一个区间,n和m最大都是10^5,也就是说最多会用到3*10^5个数,这与2*10^9比起来是十分小的,如果我们要开一个2*10^9的数组,显然有很多空间是会浪费掉的。于是,这道题使用了离散化

因为值域太大,所以将用到的下标映射到从0开始的自然数(从1开始也行),映射的过程就称为离散化

我们先开一个数组,将用到的下标全部放进这个数组中,然后再进行离散化

离散化时要注意两个问题:
(1)用到的下标可能是存在重复的,所以要先去重
(2)如何将下标映射到从0开始的自然数?此时需要用到二分,利用其在存放数组中的下标,一个数映射后的值就是其在数组中的下标

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
typedef pair<int, int> PII;
const int N = 300010;
int n, m;
int a[N], s[N]; // a是存离散化后的值的数组,s是存a的前缀和
vector<int> alls; // 存需要离散化的元素,x、l、r需要离散化,去重就是对alls去重
vector<PII> add, query; 
// add存要在x位置增加c的键值对
// query存每次询问的左右区间l和r
int find(int x)
{int l = 0, r = alls.size() - 1;while(l < r){int mid = l + r>> 1;if(alls[mid] >= x) r = mid; else l = mid + 1;}return r + 1;
}
int main()
{cin>>n>>m;for(int i = 0;i < n;i++){int x, c;cin>>x>>c;add.push_back({x, c});alls.push_back(x);}for(int i = 0;i < m;i++){int l, r;cin>>l>>r;query.push_back({l, r});alls.push_back(l);alls.push_back(r);}// 去重sort(alls.begin(), alls.end());alls.erase(unique(alls.begin(), alls.end()), alls.end());// 处理插入for(auto item : add){int x = find(item.first);a[x] += item.second;}// 预处理前缀和for(int i = 1;i <= alls.size();i++) s[i] = s[i - 1] + a[i];// 处理询问for(auto item : query){int l = find(item.first), r = find(item.second);cout<<s[r] - s[l - 1]<<endl;}return 0;
}

 这里使用任何一个二分模板均可

9、区间合并

803. 区间合并 - AcWing题库

这道题就是首先将区间全部读入到一个vector<pair<int, int>>中,然后根据区间的左端点对区间进行排序,然后对所有区间进行遍历,遍历过程中将有交集的区间进行合并,合并好的区间放入一个新的数组中

遍历:维护当前区间,遍历后面的区间,看后面区间与当前区间的关系。因为已经按区间左端点进行排序了,所以关系只有3种

#include<iostream>
#include<algorithm>
using namespace std;
typedef pair<int, int> PII;
int n;
vector<PII> segs, res; // segs读入所有区间,res存放合并好的区间
void merge(vector<PII>& segs)
{sort(segs.begin(), segs.end());int st = -2e9, ed = -2e9; // 最先开始让维护的区间位于最左边,并且第一次更新区间不能放入resfor(int i = 0;i < n;i++){if(ed < segs[i].first){if(i != 0) res.push_back({st, ed});st = segs[i].first;ed = segs[i].second;}else ed = max(ed, segs[i].second);}if(st != -2e9) res.push_back({st, ed}); // 最后还需要将最后维护的区间放入,此时要判断一下,因为有可能segs中没有任何区间segs = res;
}
int main()
{cin>>n;for(int i = 0;i < n;i++){int l, r;cin>>l>>r;segs.push_back({l, r});}merge(segs);cout<<segs.size()<<endl;return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1543564.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

基于jsp的图书馆管理系统的设计与实现 (含源码+sql+视频导入教程+文档)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1 、功能描述 基于jsp的图书馆管理系统8拥有两种角色&#xff0c;分别为管理员和学生&#xff0c;具体功能如下&#xff1a; 管理员&#xff1a;图书管理、用户管理、违规处理、权限管理、个人信息修改…

某恩加密数据爬虫逆向分析

目标网站 aHR0cHM6Ly93d3cuZW5kYXRhLmNvbS5jbi9pbmRleC5odG1s 一、抓包分析 响应数据加密 二、逆向分析 下断点&#xff0c;刷新页面 一直往下跟栈&#xff0c;发现是在这进行的加密 内部实现逻辑 本地数据获取 本文章仅提供技术分享交流学习&#xff0c;不可对目标服务器造…

OpenAI最新GPT-o1-preview测评

OpenAI最新GPT-o1-preview测评 测试之后感觉这个相对GPT4o&#xff0c;能力上升了一个大的台阶&#xff0c;思考能力极强的最强GPT模型 之前用GPT4o测试过类似的题目&#xff0c;做的效果极差&#xff0c;答案直接就是错&#xff0c;这次GPT-o1-preview居然做对了&#xff0c;逻…

Ethernet 系列(3)-- 物理层测试::IOP Test::Cable diagnostics

车载以太网物理层IOP测试&#xff0c;即互操作性测试&#xff08;Interop- erability Tests&#xff09;&#xff0c;用于验证车载以太网PHY&#xff08;通常也称为收发器&#xff09;的可靠性和检查PHY能否在给定的有限时间内建立稳定的链路;还用于车载以太网PHY的诊断&#x…

窗口函数性能提升50倍,PawSQL索引推荐实战案例

&#x1f31f;引言 在数据驱动的现代世界&#xff0c;SQL查询的速度是应用程序快速响应的关键。尤其是那些涉及窗口函数的复杂查询&#xff0c;若缺乏恰当的索引支持&#xff0c;性能瓶颈可能会成为阻碍。本文将带您看看PawSQL是如何通过智能索引推荐&#xff0c;帮助一个包含…

《深度学习》—— 神经网络中常用的激活函数

文章目录 1. Sigmoid 激活函数2. Softmax 激活函数3. ReLU 激活函数4. Leaky ReLU 激活函数5. ELU 激活函数6. Tanh 激活函数 激活函数&#xff08;Activation Function&#xff09;是在人工神经网络的神经元上运行的函数&#xff0c;负责将神经元的输入映射到输出端。它在神经…

CVE-2024-4956实战

一、访问网页 二、公司信息域名收集 三、抓包读取敏感文件 Burpsuite抓包&#xff0c;修改GET请求即可&#xff08;GET /%2F%2F%2F%2F%2F%2F%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Fetc%2Fpasswd HTTP/1.1 &#xff09;

网工想提升,不止华为HCIE这一个证书

作为网络工程师&#xff0c;拥有一张HCIE&#xff08;华为认证互联网专家&#xff09;无疑是职业生涯中的一项重要成就&#xff0c;但网络技术的世界远比一张证书要复杂得多。提升自己的技术水平&#xff0c;不仅要依赖HCIE这一张证书&#xff0c;更可以通过学习其他认证&#…

现在的大模型榜单,真就没一个可信的,真的都是水分

现在的大模型榜单上&#xff0c;真的都是水分。 全是作弊的考生&#xff0c;真的。 上周&#xff0c;AI圈有个很炸裂的大模型发布&#xff0c;在全网引起了山呼海啸&#xff0c;一众从业者和媒体尊称它为开源新王。 就是Reflection 70B。 在每项基准测试上都超过了 GPT-4o&a…

printf 命令:格式化输出

一、命令简介 ​printf​ 命令在 Linux 系统中用于格式化并打印字符串到标准输出。它是 C 语言中 printf ​函数的命令行版本&#xff0c;因此其格式化选项与 C 语言中的非常相似。 相关命令&#xff1a; echo&#xff1a;通常使用 echo&#xff0c;它比较简单。printf&…

FastAPI开发环境搭建——开发第一个web程序

FastAPI开发环境搭建——开发第一个web程序 搭建开发环境 FastAPI官方文档学习 - FastAPI (tiangolo.com) 安装fastapi框架 pip install fastapi[all] pip install uvicorn使用对应IDE创建fastapi项目&#xff0c;例如pycharm,vscode和创建普通的python项目无差别 创建一个…

Solidity编码规范汇总篇

本文首发于公众号 【Keegan小钢】 上周&#xff0c;完成了 Solidity 编码规范的视频录制并上传到了 B 站、Youtube 和视频号。总共分为了 6 个小节&#xff0c;在 B 站的合集地址为&#xff1a; https://space.bilibili.com/60539794/channel/collectiondetail?sid3780183 为…

【ASE】第一课_双面着色器

今天我们一起来学习ASE插件&#xff0c;希望各位点个关注&#xff0c;一起跟随我的步伐 今天我们来学习双面着色器&#xff0c;对颜色和贴图进行差值&#xff0c;双面显示不同的效果 最终效果&#xff1a; 思路&#xff1a; 1.先确定前后面的贴图和颜色 贴图&#xff08;Alb…

高效工程师的七个习惯

原文 我曾与一些杰出的工程师共事过 – 在诸如 FAANG 的大公司&#xff0c;也在初创规模的小公司。他们让我看到&#xff0c;传说中的「10 倍」工程师&#xff0c;真实存在&#xff01; 如今&#xff0c;这些工程师中&#xff0c;有些人后来创办了自己的公司&#xff0c;他们…

kmp快速匹配

用处&#xff1a;对于一个较长的字符串A&#xff0c;判断A中是否存在字符串B。 思路&#xff1a; 暴力的做法是从A的每个元素开始&#xff0c;依次比较看是否有和B相同的子串&#xff0c;时间复杂度是o&#xff08;N*N&#xff09; 优化思路是对于每次查找完成以后&#xff…

springboot+vue宠物医院挂号看病诊断系统 f9h46

目录 宠物主人宠物医生系统管理人员系统实现截图技术介绍核心代码部分展示详细视频演示源码获取 宠物主人 登录注册&#xff1a;注册账户并登录系统。 首页&#xff1a;显示系统基本信息和用户导向功能。 个人中心&#xff1a;更新个人信息&#xff0c;包括联系方式、密码等。…

【AI创作组】工程方向的硕士研究生学习Matlab的路径

1. MATLAB软件概述 1.1 MATLAB发展历程 MATLAB自20世纪70年代诞生以来,已经经历了多次重要的版本更新和功能扩展。 初始版本:MATLAB的前身只是一个简单的交互式矩阵计算器,由Cleve B. Moler博士在1970年代初期开发,目的是为了方便学生和研究人员使用线性代数软件包LINPAC…

农业与植物基因组分析专家—优青博导提供从实验设计、数据分析到SCI论文咨询的一站式服务。多年经验,精准高效,为农业科研保驾护航!

&#x1f31f; 教授团队领衔&#xff0c;全方位服务&#xff01; &#x1f680; 从实验设计到论文发表&#xff0c;一站式解决方案&#xff01; &#x1f4c8; 选择我们&#xff0c;加速您的科研进程&#xff0c;让成果不再等待&#xff01; &#x1f4dd; 专业分析 定制服…

ubuntu安装gitlab-runner

目录 1.添加gitlab 仓库地址 ​编辑2. 安装gitlab-runner命令 1.添加gitlab 仓库地址 curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | sudo bash2. 安装gitlab-runner命令 sudo apt-get install -y gitlab-ru…

Python3爬虫教程-HTTP基本原理

HTTP基本原理 1&#xff0c;URL组成部分详解2&#xff0c;HTTP和HTTPS3&#xff0c;HTTP请求过程4&#xff0c;请求&#xff08;Request&#xff09;请求方法&#xff08;Request Method&#xff09;请求的网址&#xff08;Request URL&#xff09;请求头&#xff08;Request H…