Content:
对快排问题的补充
高精度
前缀和与差分
Conclusion
Part1 对快排问题的补充
在昨天的文章中,主要提到了排序和二分两种算法,其实在array或是vector中进行元素之间变更的问题时,最难处理的一点无非就是如何解决分界问题,毕竟数组极易造成超出边界等问题而发生报错。
quick sort算法中,看到模板上取得的这一块内容:
int i = l - 1, j = r + 1, x = q[l + r >> 1];//>>1就相当于除2操作
while (i < j)
{
do i++; while (q[i] < x);
do j--; while (q[j] > x);//大于、小于x的元素分别进行前置后置操作
if (i < j) swap(q[i], q[j]);
}
quick_sort(q, l, j), quick_sort(q, j + 1, r);//递归
为了避免越界读取问题,我们首先取得了中间值作为分界点来调整区间,两边的起始位置为l-1与r+1,是为了进行do-while循环,否则无法考虑进边界值。此后进行双指针扫描,遇到不符合的值采用swap()进行调整,在递归时函数中引用的变量为(q,l,j)与(q,j+1,r),或者是(q,l,i-1)与(q,i,r),这里不需要考虑边界问题。
但如果我们采用了边界值进行分界调整区间,那就需要考虑这个问题了,比如说,如果采用q【r】即右边界来给x赋值,递归时函数内部的参数就应该改为(q,l,i-1)与(q,i,r),而不能使用j来分界。
Part2 高精度
高精度运算主要包含大位数之间加减和大位数与小位数之间的乘除运算。
对于大位数整数的存储,我们会用一个数组倒着存储一个整数,举例,数组a【0】代表了个位,a【1】代表十位,以此类推,也就是说,正向来看数组的话,与我们平时写数字的顺序是刚好相反的。这就是在编写数组时需要注意的一个地方。
而在这里采用vector,刚好可以用到vector.size来计算动态数组的长度,push_back也正是这个原因,计算完毕之后可以直接放到数组的末尾,不影响之前算过的位数的值,从而保证了计算的连续性。
高精度加法模板:
#include<iostream>
#include<vector>
using namespace std;
const int N = 1e6 + 10;
vector<int> add(vector<int>& A, vector<int>& B)
{
vector<int> C;
int t = 0;
for (int i = 0; i < A.size()||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(t);
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;
}
而对于高精度减法,有如下几个突破点需要我们思考:
如何处理借位问题: 面对借位问题,我们按照每一位的运算都引入一个新变量t为例,考虑以下,首先t本身的值为0,现在需要把A【i】的值赋给t,之后再把-B【i】的值赋给t,最后将t的值push_back;
如何处理算得结果为负数的情况:
我们不可能把一个小于0的数输入到数组中,这也不符合计算的规律,因此需要做的是,不论是正数还是负数,要同时+10再求得10的模,由于加10的倍数不影响模的值,所以这一步非常巧妙,可以对正负数做相同的处理,最终把mod值push_back即可。
同样,这种处理也并没有改变原先t的值,因而可以直接通过判断t的正负来赋给t流入下一轮的初始值。
高精度减法模板:
#include<iostream>
#include<vector>
using namespace std;
const int N = 1e6 + 10;
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;
for (int i = 0, t = 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();
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');
if (cmp(A, B))
{
auto C = sub(A, B);
for (int i = C.size() - 1; i >= 0; i--)printf("%d", C[i]);
}
else
{
auto C = sub(B, A);
printf("-");
for (int i = C.size() - 1; i >= 0; i--)printf("%d", C[i]);
return 0;
}
}
高精度乘法的计算类似于加法,故而不多赘述,模板:
vector<int> mul(vector<int> &A, int b)
{
vector<int> C;
int t = 0;
for (int i = 0; i < A.size() || t; i ++ )
{
if (i < A.size()) t += A[i] * b;
C.push_back(t % 10);
t /= 10;
}
return C;
}
对于大位数除法而言,与前者不同的是,除法是从高位算起,到低位为止,同时还要注意的是最终输出结果时要定义一个余数,专门储存除不尽的情况下余数的值:
模板:
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();
return C;
}
Part3 前缀和和差分
前缀和也就是数列的前n项和,这个的计算还是比较简单的。
#include<iostream>
#include<vector>
using namespace std;
const int N = 1e3 + 10;
int main()
{
int m,n;
int a[N], s[N];
scanf_s("%d %d", &m,&n);
for (int i = 0; i < n; i++)
{
scanf_s("%d", &a[i]);
s[0] = a[0];
}
for (int i = 1; i < n; i++)
{
s[i] = s[i - 1] + a[i];
}
while (m--)
{
int l, r;
scanf_s("%d %d", &l, &r);
printf("%d", s[r] - s[l-1]);
}
return 0;
}
当然,如果我们需要求一个矩阵中某一部分的和的话,考虑用二维数组进行类似前缀和运算。
差分的意思是,另设一个数组,数组中的值代表了相邻值之间的差值,而且我们可以通过这个差值来简单改变整个数组在一个区间内每个元素的值。
举个例子,我们现在有1-1000的元素,现在需要使300-600之间的值全部加2,如果我们提前设置好差分数组,我们此时只需要改变两个值,即b【300】与b【601】,因为这两个值的前后值之间距离变大,也就是差分数组的值发生了改变。同样的,只要这个时候进行b【i】+=b【i-1】的操作,就可以得到变换之后的数组。
#include<iostream>
#include<vector>
using namespace std;
const int N = 1e3 + 10;
int n, m;
int a[N], b[N];
void insert(int l, int r, int c)
{
b[l] += c;
b[r + 1] -= c;
}
int main()
{
scanf_s("%d%d", &n, &m);
for (int i = 1; i <= n; i++) scanf_s("%d", &a[i]);
for (int i = 1; i <= n; i++) insert(i,i,a[i]);
while (m--)
{
int l, r, c;
scanf_s("%d%d%d", &l, &r, &c);
insert(l, r, c);
}
for (int i = 1; i <= n; i++) b[i] += b[i - 1];
for (int i = 0; i < n; i++) printf("%d", b[i]);
return 0;
}
时间复杂度大大降低!从o(n)降至o(1)
接下来以改变一个矩阵从(x1,y1)到(x2,y2)范围内的值为例,说明一下二维数组的妙用:
#include<iostream>
#include<vector>
using namespace std;
const int N = 1e3 + 10;
int n, m, q;
int a[N][N], b[N][N];
void insert(int x1, int x2, int y1, int y2, int c) {
b[x1][y1] += c;
b[x2 + 1][y2 + 1] += c;
b[x2 + 1][y1] -= c;
b[x1][y2 + 1] -= c;
}
int main()
{
scanf_s("%d%d%d", &n, &m, &q);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
scanf_s("%d", &a[i][i]);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
insert(i, j, i, j, a[i][j]);
while (q--)
{
int x1, x2, y1, y2, c;
cin >> x1 >> x2 >> y1 >> y2 >> c;
insert(x1, y1, x2, y2, c);
}
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
b[i][j] += b[i - 1][j] + b[i][j - 1] - b[i - 1][j - 1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++)
printf("%d", b[i][j]);
puts("");
}
return 0;
}
矩阵的差分比较复杂,每一个数的差分值应该是与上面的数和左面的数相加再减去左上角的数值,因此需要在insert函数中对四个数字进行更新。
Part4 Conclusion
巧用while(q--)作为需要输出的答案个数;
vector中的push_back函数在大位数运算中非常好用;
在四则运算进行一位一位的分别运算时,引入中间量t作为进位或者余数都非常重要。
题目来源&代码参考:ACWing
感谢大家的支持,博主会继续提升文章质量,大家对文章中任何讲述不明的地方都可以留言哈,我们共同探讨,一起进步!