算法基础课(二)——高精度、前缀和与差分

2020-02-06   


高精度加法

#include<bits/stdc++.h>
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(t);
    return C;
}
vector<int>add(vector<int>&A, vector<int>&B){
    vector<int>C;
    if(A.size() < B.size()) return add(B, A);// 位数多的在前
    int t=0;
    for(int i=0; i<A.size(); i++){
        t += A[i];
        if(i<B.size()) t += B[i];// 每一位是三个数的和,两个数字一个进位
        C.push_back(t % 10);
        t /= 10;
    }
    if(t) C.push_back(t); // for(int i = 0; i < A.size() || t; i ++)
    return C;
}
int main(){
    string a,b; cin>>a>>b;
    vector<int>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');
    auto C = add(A,B);
    for(int i=C.size()-1; i>=0; i--) cout << C[i];
}

高精度减法

#include<bits/stdc++.h>
using namespace std;
bool cmp(vector<int>&A, vector<int>&B){
    // A 是否>= 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;
}
// C = A - B
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();// 去掉数字前导的 0;
    return C;
}
int main(){
    string a,b; cin>>a>>b;
    vector<int>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,计算 A - B
        auto C = sub(A,B);
        for(int i=C.size()-1; i>=0; i--) cout<<C[i];
    }
    else{ // 否则计算 B - A
        cout<<'-';
        auto C = sub(B,A);
        for(int i=C.size()-1; i>=0; i--) cout<<C[i];
    }
}

高精度乘法

#include<bits/stdc++.h>
using namespace std;

vector<int>mul(vector<int>&A, int B){
    vector<int>C;
    int t=0;// 上一位进位
    for(int i=0; i<A.size()||t; i++){ // 要么 i 没循环结束,要么 t != 0, 加 t 的判断是为了放入最后一位数字
        t += A[i] * B;
        C.push_back(t%10);
        t /= 10; //上一位进位
    }
    while(C.size()>1 && C.back()==0) C.pop_back();// 去掉数字前导的 0;
    return 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');
    auto C = mul(A,B);
    for(int i=C.size()-1; i>=0; i--) cout<<C[i];
}

高精度除法

#include<bits/stdc++.h>
using namespace std;
vector<int>div(vector<int>&A, int B, int &r){
    // 注意除法从最高位开始计算
    vector<int>C;
    for(int i=A.size()-1; i>=0; i--){
        r = r * 10 + A[i];// 竖式下拉
        C.push_back(r/B); // 试除,可能会加入多个0
        r %= B;
    }
    reverse(C.begin(), C.end());
    while(C.size()>1 && C.back()==0) C.pop_back();
    return 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=0;
    auto C = div(A,B,r);
    for(int i=C.size()-1; i>=0; i--) cout<<C[i];
    cout<<endl<<r<<endl;
    
}

数组前缀和

// 下标从 1 开始, a[1], a[2],...,a[n]
#include<iostream>
using namespace std;
const int N=100010;
int a[N],s[N];
int main(){
    // 数组前缀
    int n,q;
    cin>>n>>q;
   for(int i=1;i<=n;i++) cin>>a[i], s[i] = s[i-1]+a[i];
    while(q--){
        int l, r;
        cin>>l>>r;
        cout<<s[r]-s[l-1]<<endl;
    }
}

矩阵前缀和

前缀中动 x1,y1x_1,y_1 ,差分中动 x2,y2x_2,y_2

#include<iostream>
using namespace std;
const int N=1010;
int a[N][N], s[N][N];
int main(){
    int m,n,q;cin>>m>>n>>q;
    for(int i=1;i<=m;i++)
    for(int j=1;j<=n;j++){
        cin>>a[i][j], s[i][j] = s[i][j-1]+s[i-1][j]-s[i-1][j-1]+a[i][j];
    }
    while(q--){
        int x1,y1,x2,y2;
        cin>>x1>>y1>>x2>>y2;
        cout<<s[x2][y2] -s[x1-1][y2]-s[x2][y1-1] + s[x1-1][y1-1]<<endl;
    }
}

数组差分——前缀和的逆运算

a1,a2,...,ana_1,a_2,...,a_n,构造 b1,b2,...bnb_1,b_2,...b_n,使得 ai=b1+b2+...+bia_i=b_1+b_2+...+b_i 。于是得差分数组:

b1=a1,b2=a2a1,,bn=anan1b_1=a_1,b_2=a_2-a_1,\cdots,b_n=a_n-a_{n-1}

aabb 的前缀和,对 bb 求前缀和就可得到原数组(O(n))。

这类操作可以很好解决区间操作问题:给定一个区间,对区间中的元素进行操作,如加某个数。

差分核心:将 a[lr]a[l-r] 全部加 cc,等价于 b[l]+=cb[l] += c, b[r+1]=cb[r+1] -= c

#include<iostream>
using namespace std;
const int N=100010;
int a[N],b[N];
void insert(int l, int r, int c){
    // 原数组的[l,r]区间加 c,只需对差分数组操作就行。
    b[l] += c;
    b[r+1] -= c;
}
int main(){
    int n,q; cin>>n>>q;
    for(int i=1;i<=n;i++) cin>>a[i],insert(i,i,a[i]);// 构建
    while(q--){
        int l,r,c;
        cin>>l>>r>>c;
        insert(l,r,c);
    }
    for(int i=1;i<=n;i++) b[i]+=b[i-1],cout<<b[i]<<' ';
}

矩阵差分

给子矩阵加上一个数。

#include<iostream>
using namespace std;
const int N=1010;
int a[N][N],b[N][N];
void insert(int x1,int y1,int x2,int y2,int c){
    b[x1][y1] += c;
    b[x1][y2+1] -=c;
    b[x2+1][y1] -=c;
    b[x2+1][y2+1] +=c;
}
int main(){
    int m,n,q;cin>>m>>n>>q;
    for(int i=1;i<=m;i++)
    for(int j=1;j<=n;j++){
        cin>>a[i][j],insert(i,j,i,j,a[i][j]);
    }
    while(q--){
        int x1,y1,x2,y2,c;cin>>x1>>y1>>x2>>y2>>c;
        insert(x1,y1,x2,y2,c);
    }
    for(int i=1;i<=m;i++){
    for(int j=1;j<=n;j++){
        b[i][j] += b[i-1][j] +b[i][j-1] -b[i-1][j-1];
        cout<<b[i][j]<<" ";
    }
    puts("");
    }
}

Q.E.D.


我是星,利剑开刃寒光锋芒的银星,绝不消隐