第一章 基础算法
高精度 算法4:高精度(只有C++需要),一般有四种情况:
两个大整数相加 A + B,A、B的位数 $\le 10^6$; 
两个大整数相减 A - B,A、B的位数 $\le 10^6$; 
一个大整数乘以一个小整数 A * a,A的位数$\le 10^6$,a$\le 10000$; 
一个大整数除以一个小整数 A / a,A的位数$\le 10^6$,a$\le 10000$; 
 
首先要考虑的是一个大整数如何存储?方法是可以将其中的每一位保存在一个数组中,为了方便运算,让a[0]存数字的个位,a[1]存数字的十位……依次存高位。如数字以string类型输入a = "123456",用vector<int>来存储,A=[6,5,4,3,2,1]。
(1)加法
两个数组的加法运算就是来模拟人工手算的过程,从个位开始逐位相加,逢十进一。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <iostream>  #include <vector>  using  namespace  std ;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);      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 ; } 
 
还可以在空间上做进一步的优化,即进行压位处理,数组中的每一个元素不止存放数字的一位,而是多位。(不常用)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 #include  <iostream>  #include  <vector>  using  namespace  std ;const  int  base = 1000000000 ;vector <int > add(vector <int > &A, vector <int > &B){     if  (A.size () < B.size ()) return  add(B, A);     vector <int > C;     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 % base);         t /= base;     }     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 , s = 0 , j = 0 , t = 1 ; i >= 0 ; i -- )     {         s += (a[i] - '0' ) * t;         j ++, t *= 10 ;         if  (j == 9  || i == 0 )         {             A.push_back(s);             s = j = 0 ;             t = 1 ;         }     }     for  (int  i = b.size () - 1 , s = 0 , j = 0 , t = 1 ; i >= 0 ; i -- )     {         s += (b[i] - '0' ) * t;         j ++, t *= 10 ;         if  (j == 9  || i == 0 )         {             B.push_back(s);             s = j = 0 ;             t = 1 ;         }     }     auto  C = add(A, B);     cout  << C.back();     for  (int  i = C.size () - 2 ; i >= 0 ; i -- ) printf ("%09d" , C[i]);     cout  << endl ;     return  0 ; } 
 
(2)减法
两个数组的减法运算也是来模拟人工手算的过程,从个位开始逐位相减,不够就向前一位借1,加10。
算法的思路:
如果A$\ge$B,就计算$A - B$;如果A$<$B,就计算 $-(B - A)$。 
在每一位上,若$A_i-B_i-t \ge 0$,就计算$A_i-B_i-t$;若$A_i-B_i-t < 0$,就计算$A_i-B_i+10-t $,其中$t$代表借位。 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 #include <iostream>  #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();        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))     {         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 ; } 
 
(3)乘法
也是模拟手算乘法,区别是:逐位相乘时是乘以整个被乘数b。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <iostream>  #include <vector>  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++)        {         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();            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--)  printf ("%d" , C[i]);     return  0 ; } 
 
(4)除法
同样的思路,模拟手算的过程,注意区别:除法是从最高位开始除的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <iostream>  #include <vector>  #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();     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;     auto  C = div(A, b, r);          for (int  i = C.size () - 1 ; i >= 0 ; i--)  printf ("%d" ,C[i]);     cout  << endl  << r << endl ;          return  0 ; } 
 
前缀和,差分 算法5:前缀和,差分
 前缀和:设一个数组$a_1,a_2,a_3,\dots,a_n$,(注意下标从1开始),定义其前缀和为$S_i=a_1+a_2+\dots+a_i$,规定$S_0=0$。
如何求前缀和$S_i$:for遍历即可; 
前缀和的作用:可以方便地求出序列中某一段的和,如求下标区间$[l,r]$内的元素的和,即可用$S_r-S_{l-1}$,时间复杂度为$O(1)$。 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <iostream>  using  namespace  std ;const  int  N = 100010 ;int  n, m;int  a[N], s[N];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++) s[i] = s[i - 1 ] + a[i];               while (m--)     {         int  l, r;         scanf ("%d%d" , &l, &r);         printf ("%d\n" , s[r] - s[l - 1 ]);         }          return  0 ; } 
 
前缀和也可以扩展到二维,求区间和$\to$求子矩阵和。用$S_{ij}$表示左上角的子矩阵的和。如下图若要求以$(x_1,y_1)$为左上角,以$(x_2,y_2)$为右下角的子矩阵的和,那就可以转化成求:
如何求子矩阵和$S_{ij}$:两层for遍历i,j,
主要的思想就是容斥原理 。
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <iostream>  using  namespace  std ;const  int  N = 1010 ;int  n, m, q;int  a[N][N], s[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]);             s[i][j] = s[i - 1 ][j] + s[i][j - 1 ] - s[i - 1 ][j - 1 ] + a[i][j];         }          while (q--)     {         int  x1, y1, x2, y2;         scanf ("%d%d%d%d" , &x1, &y1, &x2, &y2);         printf ("%d\n" , s[x2][y2] - s[x1 - 1 ][y2] - s[x2][y1 - 1 ] + s[x1 - 1 ][y1 - 1 ]);     }     return  0 ; } 
 
差分是前缀和的逆运算 ,设一个数组$a_1, a_2, \dots, a_n$,现构造一个数组$b_1,b_2,\dots, b_n$,使得$a_i=b_1+b_2+\dots+b_n$,即$a$数组的元素是$b$数组的前缀和,$b$数组的元素是$a$数组的差分 。则有:
差分的作用:
若有$b$数组,就可以通过求前缀和的方法求得原数组$a$,时间复杂度$O(n)$。 
若要对$a$数组下标为$[l,r]$区间的一段元素都加上$c$,则要$O(n)$的时间复杂度;若考虑改动$b$数组,那只要改变两个元素,即让$b_l+c$,让$b_{r+1}-c$,则只要$O(1)$的时间复杂度,这样由数组$b$得到的数组$a$的下标为$[l,r]$的一段就都加上了$c$。 
那若有了数组$a$,如何得到数组$b$:可以假设数组$a$初始全部是0,依次在区间[1,1]加上$a_1$,在区间[2,2]加上$a_2$,……,在区间[n,n]加上$a_n$,即转换到对数组$b$的操作。 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <iostream>  using  namespace  std ;const  int  N = 100010 ;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 ("%d%d" , &n, &m);     for (int  i = 1 ; i <= n; i++)      {         scanf ("%d" , &a[i]);         insert(i, i, a[i]);         }     while (m--)     {         int  l, r, c;         scanf ("%d%d%d" , &l, &r, &c);         insert(l, r, c);     }     for (int  i = 1 ; i <= n; i++)     {         b[i] += b[i-1 ];            printf ("%d " , b[i]);     }     return  0 ; } 
 
差分也有二维的形式,原矩阵元素$a_{ij}$,差分矩阵元素$b_{ij}$,使得$a_{ij}$是差分矩阵$b_{ij}$的前缀和。
其作用也可由上面的一维差分类比过来:给矩阵$a$的某一个子矩阵(左上角为$(x_1,y_1)$,右下角为$(x_2,y_2)$)中的元素全都加上一个数$c$,可以转化成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <iostream>  using  namespace  std ;const  int  N = 1010 ;int  n, m, q;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--)     {         int  x1, y1, x2, y2, c;         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++)             {                 b[i][j] += b[i-1 ][j] + b[i][j-1 ] - b[i-1 ][j-1 ];                 printf ("%d " , b[i][j]);             }             printf ("\n" );         }     return  0 ; }