算法竞赛入门(C++)

(一)从C到C++

(一)、框架

  1. 虽然C语言中大多数头文件在C++中仍然可以使用,但推荐方法是在C头文件之前加小写c,在之后去掉.h
  2. C++中可以使用流简化输入输出操作。标准输入输出流在头文件iostream中定义,存在于namespace(std)中。如果使用了 using namespace std 则直接使用。

(二)、引用


int n = 4;
int & r = n;
r = 4; 
cout << r; //输出
4
cout << n; //输出
4
n = 5;
cout << r; //输出5 

  1. 格式 : 类型名 & 引用名 = 某变量名;
  2. 某个变量的引用,等价于这个变量,相当于该变量的一个别名。
  3. 引用只能引用变量,不能引用常量和表达式。
  4. 常引用:
    定义引用时,前面加const关键字,即为“常引用”
int n = 100;
const int & r = n;
r = 200; //编译错
n = 300; // 没问题

注:

  • const T & 和T & 是不同的类型!!!

  • 不能通过常引用去修改其引用的内容

(三)“const”关键字的用法
1 . 定义常量

const int MAX_VAL = 23;
const string SCHOOL_NAME = "Peking University";

2 .定义常量指针


int n,m;
const int * p = & n;
* p = 5; //编译出错
n = 4; //ok
p = &m; //ok, 常量指针的指向可以变化

  • 不可通过常量指针修改其指向的内容
  • 不能把常量指针赋值给非常量指针,反过来可以

3 .定义常引用


int n;
const int & r = n;
r = 5; //error
n = 4; //ok

(四)、字符串

#include 
#include 
 
using namespace std;
 
int main ()
{
   string str1 = "Hello";
   string str2 = "World";
   string str3;
   int  len ;
 
   // 复制 str1 到 str3
   str3 = str1;
   cout << "str3 : " << str3 << endl;
 
   // 连接 str1 和 str2
   str3 = str1 + str2;
   cout << "str1 + str2 : " << str3 << endl;
 
   // 连接后,str3 的总长度
   len = str3.size();
   cout << "str3.size() :  " << len << endl;
 
   return 0;
}
  1. C++在string头文件里定义了string类型,直接支持流式读写。
  2. 可以把string作为流进行读写,定义在sstream中。

(五)结构体

  • C++中的结构体可以有一个或多个构造函数,在声明变量时调用
  • C++中的函数(不只是构造函数)参数可以拥有默认值
  • 在C++结构体的成员函数中,this是值向当前函数的指针

(六)模板

基本框架:

template  ret-type func-name(parameter list)
{
   // 函数的主体
}
  • 对所有的类型均可调用,故可以避免函数的重复定义。
//举例
#include 
#include 
 
using namespace std;
 
template 
inline T const& Max (T const& a, T const& b) 
{ 
    return a < b ? b:a; 
} 
int main ()
{
 
    int i = 39;
    int j = 20;
    cout << "Max(i, j): " << Max(i, j) << endl; 
 
    double f1 = 13.5; 
    double f2 = 20.7; 
    cout << "Max(f1, f2): " << Max(f1, f2) << endl; 
 
    string s1 = "Hello"; 
    string s2 = "World"; 
    cout << "Max(s1, s2): " << Max(s1, s2) << endl; 
 
   return 0;
}

(二)STL简记

(1)vector, 变长数组,倍增的思想

  • size() 返回元素个数
  • empty() 返回是否为空
  • clear() 清空
  • front()/back()
  • push_back()/pop_back()
  • begin()/end()
  • []
  • 支持比较运算,按字典序

(2)pair

  • first, 第一个元素
  • second, 第二个元素
  • 支持比较运算,以first为第一关键字,以second为第二关键字(字典序)

(3)string,字符串

  • size()/length() 返回字符串长度
  • empty()
  • clear()
  • substr(起始下标,(子串长度)) 返回子串
  • c_str() 返回字符串所在字符数组的起始地址

(4)queue, 队列

  • size()
  • empty()
  • push() 向队尾插入一个元素
  • front() 返回队头元素
  • back() 返回队尾元素
  • pop() 弹出队头元素

(5)priority_queue, 优先队列,默认是大根堆

  • push() 插入一个元素
  • top() 返回堆顶元素
  • pop() 弹出堆顶元素
  • 定义成小根堆的方式:priority_queue q;

(6)stack, 栈

  • size()
  • empty()
  • push() 向栈顶插入一个元素
  • top() 返回栈顶元素
  • pop() 弹出栈顶元素

(7)deque, 双端队列

  • size()
  • empty()
  • clear()
  • front()/back()
  • push_back()/pop_back()
  • push_front()/pop_front()
  • begin()/end()
  • []

(8)set, map, multiset, multimap, 基于平衡二叉树(红黑树),动态维护有序序列

  • size()
  • empty()
  • clear()
  • begin()/end()
  • ++, – 返回前驱和后继,时间复杂度 O(logn)

1、set/multiset

  • insert() 插入一个数

  • find() 查找一个数

  • count() 返回某一个数的个数

  • erase()

    (1) 输入是一个数x,删除所有x O(k + logn)
    (2) 输入一个迭代器,删除这个迭代器

  • lower_bound()/upper_bound()

  • lower_bound(x) 返回大于等于x的最小的数的迭代器

  • upper_bound(x) 返回大于x的最小的数的迭代器

2、map/multimap

  • insert() 插入的数是一个pair
  • erase() 输入的参数是pair或者迭代器
  • find()
  • [] 注意multimap不支持此操作。 时间复杂度是 O(logn)
    lower_bound()/upper_bound()

(9)unordered_set, unordered_map, unordered_multiset, unordered_multimap, 哈希表

  • 和上面类似,增删改查的时间复杂度是 O(1)
  • 不支持 lower_bound()/upper_bound(), 迭代器的++,–

(10)bitset, 压位

bitset<10000> s;
~, &, |, ^
>>, <<
==, !=
[]

  • count() 返回有多少个1

  • any() 判断是否至少有一个1
    none() 判断是否全为0

  • set() 把所有位置成1
    set(k, v) 将第k位变成v
    reset() 把所有位变成0
    flip() 等价于~
    flip(k) 把第k位取反

(三)基础算法

(1)高精度计算

补充:C++各种数据类型范围

类型 范围
short -32768~32767 ( 2字节 )
unsigned int 0~4294967295 (4字节)
int -2147483648~2147483647 ( 4字节)
unsigned long 0~4294967295 (4字节)
long long ( int ) -9223372036854775808~9223372036854775807 (4字节)
float (-3.4E+38)~(3.4E+38) 4字节
double ( -1.7E+308)~(1.7E+308) 8字节

高精度计算中需要处理好以下几个问题:
1数据的接收方法和存贮方法:
数据的接收和存贮:当输人的数很长时,可采用字符串方式输人,这样可输入位数很长的数,利用字符串函数和操作运算,将每一位数取出,存入数组中。

2)高精度数位数的确定:
位数的确定:接收时往往是用字符串的,所以它的位数就等于字符串的长度。
3)进位,借位处理
算法竞赛入门(C++)_第1张图片
4)商和余数的求法
商和余数处理:视被除数和除数的位数情况进行处理。

// C = A + B, A >= 0, B >= 0
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 % 10);
        t /= 10;
    }
    if (t) C.push_back(t);
    return C;
}
// C = A - B, 满足A >= B, A >= 0, B >= 0
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;
}
// C = A * b, A >= 0, b > 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;
}
// A / b = C ... r, A >= 0, b > 0
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;
}

(2)选择排序

基本思想:
每一趟从待排序的数据元素中选出最小(或最大)的一个元素,顺序放在待排序的数列的最前,直到全部待排序的数据元素排完。
(2)排序过程:
初始关键字[49 38 65 97 76 13 27 49]
第一趟排序后13 [38 65 97 76 49 27 49]
第二趟排序后13 27 [65 97 76 49 38 49]
第三趟排序后13 27 38 [97 76 49 65 49]
第四趟排序后13 27 38 49[76 97 65 49]
第五趟排序后13 27 38 49 49 [97 65 76]
第六趟排序后13 27 38 49 49 65 [97 76]
第七趟排序后13 27 38 49 49 65 76 [97]
最后排序结果13 27 38 49 49 65 76 97

#include
using namespace std;
const int MAX = 1001;
int main(){
 int num,arr[MAX],temp;
 cin >> num;
 for(int i=0;i<num;i++){ //输入数据 
  cin >> arr[i];
 }
  for(int j = 0;j<num;j++){ 
  int minn = j; //定义最小值下标,默认为开始遍历位数 
  for(int k=j+1;k<num;k++){
   if(arr[minn]>arr[k]){ //找到最小值下标 
    minn = k;
   }
  }
    if(minn != j){ //使用下表交换位置 
   temp = arr[j];
   arr[j] = arr[minn];
   arr[minn] = temp;
  }
  for(int i=0;i<num-1;i++){ //输出操作细节 
  cout << arr[i]<<' ';
  }
  cout <<arr[7]<<endl;
 }
  return 0;
} 

(3)快速排序

基本方法:

  1. 确定一个数列的固定值x,可以为 p[l] , p[r] , p[l+r>>1]
  2. 将小于x的值放在x左,大于的放在x右
  3. 递归实现
#include
#include
using namespace std;
const int N = 1e6 +10;
int q[N];
//快速排序函数 
void quick_sort(int q[], int l, int r)
{
    if (l >= r) return;
    int i = l - 1, j = r + 1, x = q[l + r >> 1];	  
    while (i < j)
    {
        do i ++ ; while (q[i] < x);
        do j -- ; while (q[j] > x);
        if(i<j) swap(q[i],q[j]);
     }
     //递归 
     quick_sort(q, l, j);
     quick_sort(q, j + 1, r);//注意:此处为 j+1 ,不可写作 i+1;
} 
//主函数 
int main(){
 int n;
 scanf("%d",&n);
 for(int i=0;i<n;i++) scanf("%d",&q[i]);
 quick_sort(q,0,n-1);
 for(int j=0;j<n;j++) printf("%d ",q[j]);
 return 0;
}

(4)归并排序

基本方法:

  1. 确定分界点
  2. 递归排序
  3. 归并,合二为一
#include
#include
using namespace std;
const int maxn= 1e6+10;
//定义两个数组 
int q[maxn],tmp[maxn];
//归并函数 
void merge_sort(int q[], int l, int r)
{
    if (l >= r) return;
    int mid = l + r >> 1;
    //递归
    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 ++ ];
  //剩余数组导入 
    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]; //注意此处必须写为 i <= j;
}
//主函数 
int main(){
 int num;
 scanf("%d",&num);
 for(int i=0;i<num;i++) scanf("%d",&q[i]); 
 merge_sort(q,0,num-1);
 for(int i=0;i<num;i++) printf("%d ",q[i]); 
 return 0;
}

(5)二分

  1. 整数二分
    整数二分具体分为左寻(即符合条件的范围在分界点左侧)和右寻(即符合条件的范围在分界点右侧)两种解决方式,根据具体情况选择合适途径:
bool check(int x) {/* ... */} // 检查x是否满足某种性质

// 右寻:
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;
}
// 左寻:
int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1;//注意此时中间值取 l+r+1>>1 
        /*(原因是C++通常向下取整,当l取值为r-1时此循环变为死循环)*/
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}
  1. 浮点数二分
bool check(double x) {/* ... */} // 检查x是否满足某种性质

double bsearch_3(double l, double r)
{
    const double eps = 1e-6;   // eps 表示精度,取决于题目对精度的要求(通常为结果精确值后两位)
    while (r - l > eps)
    {
        double mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;
    }
    return l;
}

(6)前缀和

避免使用普通方法,即不可从左到右依次遍历,这种情况下一定会导致超时

#include
#include
using namespace std;
vector<int> s(100010,0);
int main(){
    int n,m;
    cin>>n>>m;
    int a;
    for(int i=1;i<=n;i++) { //此处必须注意 i 从 1 开始
     cin>>a;
     s[i]=s[i-1]+a;
 }
     while(m--){
        int l,r;
        cin>>l>>r;
        cout<<(s[r]-s[l-1])<<endl;
    }
    return 0;
}

二维应用:子矩阵的和

#include
#include
using namespace std;
const int N = 1010;
int a[N][N];
int m,n,q;
int main(){
 scanf("%d%d%d",&m,&n,&q); 
 for(int i=1;i<=m;i++)
  for(int j=1;j<=n;j++){
   int n;
   scanf("%d",&n);
   a[i][j] =a[i-1][j] + a[i][j-1] - a[i-1][j-1]+ n; //输入的同时构造和矩阵 
 }
 while(q--){
  int fl,fr,sl,sr;
  scanf("%d%d%d%d",&fl,&fr,&sl,&sr);
  printf("%d\n",a[sl][sr]-a[sl][fr-1]-a[fl-1][sr]+a[fl-1][fr-1]); //输出要求值,关键在于 fl,fr 加一 
 }
 return 0;
}

(7)差分

查分实质上是前缀和的逆运算,如差分序列 A[i] 表示原始数列 B[i] 与 B[i-1] 的差

#include
using namespace std;
const int maxn = 1e6+10;
int q[maxn]={0},p[maxn]={0};
int m,n;
 scanf("%d%d",&n,&m);
 for(int i=1;i<=n;i++){ //注意 i 值从 1 开始,i 需要满足小于等于 n
  scanf("%d",&q[i]);
  p[i] = q[i] -q[i-1];
 }
while(m--){
  int l,r,x;
  scanf("%d%d%d",&l,&r,&x);
  p[l] += x;
  p[r+1] -= x;
 }
 for(int i=1;i<=n;i++){
  q[i] = q[i-1] + p[i]; //注意 q[i] 的值是 q[i-1] 与 p[i] 的和
  printf("%d ",q[i]);
 }
 return 0;
} 

二维差分方法

#include 
#include 
using namespace std;
const int maxn = 1e3 + 40;
int a[maxn][maxn], b[maxn][maxn];

inline 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(void) {
    int n, m, q;
    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];
            if(j == m) printf("%d\n", b[i][j]);
            else printf("%d ", b[i][j]);
        } 

    return 0;
}

(8)双指针算法

常见问题分类:

  1. 对于一个序列,用两个指针维护一段区间
  2. 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作
for (int i = 0, j = 0; i < n; i ++ )
{
    while (j < i && check(i, j)) j ++ ;
    // 具体问题的逻辑
}

算法竞赛入门(C++)_第2张图片

#include
using namespace std;
const int maxn = 1e5 +10;
int a[maxn],s[maxn];
int main(){
 int n = 0, res = 0;
 cin >> n;
    for(int i = 0; i < n; i ++ ) scanf("%d", &a[i]);
    for(int i = 0, j = 0; i < n; i ++ )
 {
  s[a[i]] ++;
        while(j <= i && s[a[i]] > 1) 
  {
   s[a[j]] --; //注意此处为a[j]
   j ++;
  }
  res = max(res,i-j+1);
 }
 cout <<res<<endl;
 return 0;
}

算法竞赛入门(C++)_第3张图片

#include
using namespace std;
const int maxn = 1e6 +10;
int a[maxn]={0},b[maxn]={0};
int main(){
 int na,nb,x,tmp,cha=1;
 cin >> na>>nb>>x;
 for(int i=0;i<na;i++) cin>>a[i];
 for(int i=0;i<nb;i++) cin >>b[i];
 int i=0,j=0;
 if(a[0]>b[0]) 1;
 else{
  for(int k=0;k<max(na,nb);k++) swap(a[k],b[k]);
  tmp = na;
  na = nb;
  nb = tmp;
  cha = 0;
 }
 for(i=0,j=nb-1;i<na;i++){
  while(b[j]+a[i]>x){
   j--;
  }
  if(a[i]+b[j]==x) break;
 }
 if(cha) cout <<i<<' '<<j<<endl;
 else cout <<j<<' '<<i<<endl;
} 

(9)位运算

常见问题:

  1. 求n的第k位数字:n>> k& 1(将n右移k位后,逻辑与运算 )
  2. 求n二进制1的数目: n -= (n& -n) (此处n& -n表示n含1最后一位,此操作后n减去n的最后为1的一位)

算法竞赛入门(C++)_第4张图片

#include
using namespace std;
int main(){
 int n,x;
 cin >> n;
 for(int i=0;i<n;i++){
  int ech = 0;
  cin >> x;
  while(x)  x -=( x &(-x)),ech++;
  cout << ech << ' ';
 }
 cout <<endl;
 return 0;
}

(10)区间和

vector<int> alls; // 存储所有待离散化的值
sort(alls.begin(), alls.end()); // 将所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end());   // 去掉重复元素

// 二分求出x对应的离散化的值
int find(int x) // 找到第一个大于等于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; // 映射到1, 2, ...n
}

(11)区间合并

// 将所有存在交集的区间合并
void merge(vector<PII> &segs)
{
    vector<PII> res;
    sort(segs.begin(), segs.end());
    int st = -2e9, ed = -2e9;
    for (auto seg : segs)
        if (ed < seg.first)
        {
            if (st != -2e9) res.push_back({st, ed});
            st = seg.first, ed = seg.second;
        }
        else ed = max(ed, seg.second);
    if (st != -2e9) res.push_back({st, ed});
    segs = res;
}

(四)数据结构

(1)单链表

// head 表示头结点的下标
// e[i] 表示节点i的值
// ne[i] 表示节点i的next指针是多少
// idx 存储当前已经用到了哪个点
int head, e[N], ne[N], idx;

// 初始化
void init()
{
    head = -1;
    idx = 0;
}

// 将x插到头结点
void add_to_head(int x)
{
    e[idx] = x, ne[idx] = head, head = idx ++ ;
}

// 将x插到下标是k的点后面
void add(int k, int x)
{
    e[idx] = x, ne[idx] = ne[k], ne[k] = idx ++ ;
}

// 将下标是k的点后面的点删掉
void remove(int k)
{
    ne[k] = ne[ne[k]];
}

(2)双链表

// e[]表示节点的值,l[]表示节点的左指针,r[]表示节点的右指针,idx表示当前用到了哪个节点
int e[N], l[N], r[N], idx;

// 初始化
void init()
{
    //0是左端点,1是右端点
    r[0] = 1, l[1] = 0;
    idx = 2;
}

// 在节点a的右边插入一个数x
void insert(int a, int x)
{
    e[idx] = x;
    l[idx] = a, r[idx] = r[a];
    l[r[a]] = idx, r[a] = idx ++ ;
}

// 删除节点a
void remove(int a)
{
    l[r[a]] = l[a];
    r[l[a]] = r[a];
}

(3)栈

// tt表示栈顶
int stk[N], tt = 0;

// 向栈顶插入一个数
stk[ ++ tt] = x;

// 从栈顶弹出一个数
tt -- ;

// 栈顶的值
stk[tt];

// 判断栈是否为空
if (tt > 0)
{

}

单调栈 ——
常见模型:找出每个数左边离它最近的比它大/小的数

int tt = 0;
for (int i = 1; i <= n; i ++ )
{
    while (tt && check(stk[tt], i)) tt -- ;
    stk[ ++ tt] = i;
}

(4)队列

  1. 普通队列:
// hh 表示队头,tt表示队尾
int q[N], hh = 0, tt = -1;

// 向队尾插入一个数
q[ ++ tt] = x;

// 从队头弹出一个数
hh ++ ;

// 队头的值
q[hh];

// 判断队列是否为空
if (hh <= tt)
{

}
  1. 循环队列
// hh 表示队头,tt表示队尾的后一个位置
int q[N], hh = 0, tt = 0;

// 向队尾插入一个数
q[tt ++ ] = x;
if (tt == N) tt = 0;

// 从队头弹出一个数
hh ++ ;
if (hh == N) hh = 0;

// 队头的值
q[hh];

// 判断队列是否为空
if (hh != tt)
{

}

单调队列 ——
常见模型:找出滑动窗口中的最大值/最小值

int hh = 0, tt = -1;
for (int i = 0; i < n; i ++ )
{
    while (hh <= tt && check_out(q[hh])) hh ++ ;  // 判断队头是否滑出窗口
    while (hh <= tt && check(q[tt], i)) tt -- ;
    q[ ++ tt] = i;
}

(5)KMP算法

// s[]是长文本,p[]是模式串,n是s的长度,m是p的长度

//求模式串的Next数组:
for (int i = 2, j = 0; i <= m; i ++ )
{
    while (j && p[i] != p[j + 1]) j = ne[j];
    if (p[i] == p[j + 1]) j ++ ;
    ne[i] = j;
}

// 匹配
for (int i = 1, j = 0; i <= n; i ++ )
{
    while (j && s[i] != p[j + 1]) j = ne[j];
    if (s[i] == p[j + 1]) j ++ ;
    if (j == m)
    {
        j = ne[j];
        // 匹配成功后的逻辑
    }
}

(三)搜索

(1)DFS

(2)BFS

(四)动态规划

(1)背包问题

1.基础思路

算法竞赛入门(C++)_第5张图片

2. 01背包问题

算法竞赛入门(C++)_第6张图片
代码:

#include
#include
using namespace std;
const int N = 1e3 +10;
int f[N]; //背包储存值
int n,x;
int v[N],m[N]; 
int main(){
    cin >> n >> x;
    for(int i=1;i<=n;i++) cin >>v[i]>>m[i];
    for(int i=1;i<=n;i++)
        for(int j = x;j>=v[i];j--)
            f[j] = max(f[j],f[j-v[i]]+m[i]); //取含i与不含i的最大值
    cout << f[x] << endl;
    return 0;
}

3.完全背包问题

算法竞赛入门(C++)_第7张图片

#include 
#include 
using namespace std;
const int N = 1010;
int n, m;
int v[N], w[N];
int f[N];
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ ) cin >> v[i] >> w[i];
    for (int i = 1; i <= n; i ++ )
        for (int j = v[i]; j <= m; j ++ )
            f[j] = max(f[j], f[j - v[i]] + w[i]);
    cout << f[m] << endl;
    return 0;
}

4.多重背包问题

算法竞赛入门(C++)_第8张图片

#include 
#include 
using namespace std;
const int N = 110;
int n, m;
int v[N], w[N], s[N];
int f[N][N];
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ ) cin >> v[i] >> w[i] >> s[i];
    for (int i = 1; i <= n; i ++ )
        for (int j = 0; j <= m; j ++ )
            for (int k = 0; k <= s[i] && k * v[i] <= j; k ++ )
                f[i][j] = max(f[i][j], f[i - 1][j - v[i] * k] + w[i] * k);
    cout << f[n][m] << endl;
    return 0;
}

5.多重背包问题Ⅱ

算法竞赛入门(C++)_第9张图片

#include 
#include 
using namespace std;
const int N = 12010, M = 2010;
int n, m;
int v[N], w[N];
int f[M];
int main()
{
    cin >> n >> m;
    int cnt = 0;
    for (int i = 1; i <= n; i ++ )
    {
        int a, b, s;
        cin >> a >> b >> s;
        int k = 1;
        while (k <= s)
        {
            cnt ++ ;
            v[cnt] = a * k;
            w[cnt] = b * k;
            s -= k;
            k *= 2;
        }
        if (s > 0)
        {
            cnt ++ ;
            v[cnt] = a * s;
            w[cnt] = b * s;
        }
    }
    n = cnt;
    for (int i = 1; i <= n; i ++ )
        for (int j = m; j >= v[i]; j -- )
            f[j] = max(f[j], f[j - v[i]] + w[i]);
    cout << f[m] << endl;
    return 0;
}

6.多组背包问题

算法竞赛入门(C++)_第10张图片

#include 
#include 
using namespace std;
const int N = 110;
int n, m;
int v[N][N], w[N][N], s[N];
int f[N];
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ )
    {
        cin >> s[i];
        for (int j = 0; j < s[i]; j ++ )
            cin >> v[i][j] >> w[i][j];
    }
    for (int i = 1; i <= n; i ++ )
        for (int j = m; j >= 0; j -- )
            for (int k = 0; k < s[i]; k ++ )
                if (v[i][k] <= j)
                    f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);
    cout << f[m] << endl;
    return 0;
}

(五)贪心

(六)数论

(1)质数

质数(素数):在大于一的整数中只包含1和本身两个约数。

1.质数的判定——试除法

时间复杂度 O(sprt(n)

bool is_prime(int n)
{
	if(n<2) return false;
	for(int i = 2;i<=n/i; i++) //此处 i<=n/i 表示 i<=sprt(n) 但由于sprt()运行耗时长,不采用此方法;
		if(n % i == 0)
			return false;
	return true;
 }

2.分解质因数

  1. 试除法时间复杂度 O(sprt(n))
void divide(int x)
{
	for(int i=2;i<=n/i;i++) //n中最多只包含一个大于sqrt(n)的质因子,所以不必暴力枚举;
		if(n%i==0)
		{
			int s=0;
			while( n%i == 0)
			{
				n /= i;
				s ++ ;
			}
			printf("%d %d\n",i,s); //最后输出质因数及其在n中的次数;
		}
	if(n>1) printf("%d %d\n",n,1);
	puts("");
}
  1. 埃氏筛法
    定理 :1~n中有 n/ln n 个质数;
    时间复杂度: O(n*loglogn)
int primes[N],cnt;
bool st[N];
void get_primes(int n)
{
	for(int i = 2;i<=n;i++)
	{
		if(!st[i){
			primes[cnt++] = n;
			for(int j=i+i;j<=n;j+=i) st[j] = true; //去除该质数所有的倍数;
		}
	}
}	
  1. 线性筛法
    原理:n只会被最小质因子筛掉;
void get_primes(int n){
	for(int i=2;i<=n;i++)
	{
		if(!st[i]) primes[cnt ++] = i;
		for(int j = 0;primes[j] <= n/i;j++)
		{
			st[primes[j]*i] = true;
			if(i %primes[j] == 0) break; //primes[j]一定是 i的最小质因子
		}
	}
}

(七)图论

你可能感兴趣的:(【Note】)