(一)从C到C++
(一)、框架
- 虽然C语言中大多数头文件在C++中仍然可以使用,但推荐方法是在C头文件之前加小写c,在之后去掉.h
- 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
- 格式 : 类型名 & 引用名 = 某变量名;
- 某个变量的引用,等价于这个变量,相当于该变量的一个别名。
- 引用只能引用变量,不能引用常量和表达式。
- 常引用:
定义引用时,前面加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 <iostream>
#include <string>
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;
}
- C++在string头文件里定义了string类型,直接支持流式读写。
- 可以把string作为流进行读写,定义在sstream中。
(五)结构体
- C++中的结构体可以有一个或多个构造函数,在声明变量时调用
- C++中的函数(不只是构造函数)参数可以拥有默认值
- 在C++结构体的成员函数中,this是值向当前函数的指针
(六)模板
基本框架:
template <class type> ret-type func-name(parameter list)
{
// 函数的主体
}
- 对所有的类型均可调用,故可以避免函数的重复定义。
//举例
#include <iostream>
#include <string>
using namespace std;
template <typename T>
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<int, int>
- 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<int, vector, greater> 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)进位,借位处理
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<iostream>
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)快速排序
基本方法:
- 确定一个数列的固定值x,可以为 p[l] , p[r] , p[l+r>>1]
- 将小于x的值放在x左,大于的放在x右
- 递归实现
#include<iostream>
#include<cstdio>
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)归并排序
基本方法:
- 确定分界点
- 递归排序
- 归并,合二为一
#include<iostream>
#include<cstdio>
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)二分
- 整数二分
整数二分具体分为左寻(即符合条件的范围在分界点左侧)和右寻(即符合条件的范围在分界点右侧)两种解决方式,根据具体情况选择合适途径:
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;
}
- 浮点数二分
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<iostream>
#include<vector>
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<iostream>
#include<vector>
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<iostream>
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 <iostream>
#include <cstdio>
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)双指针算法
常见问题分类:
- 对于一个序列,用两个指针维护一段区间
- 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作
for (int i = 0, j = 0; i < n; i ++ )
{
while (j < i && check(i, j)) j ++ ;
// 具体问题的逻辑
}
#include<iostream>
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;
}
#include<iostream>
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)位运算
常见问题:
- 求n的第k位数字:n>> k& 1(将n右移k位后,逻辑与运算 )
- 求n二进制1的数目: n -= (n& -n) (此处n& -n表示n含1最后一位,此操作后n减去n的最后为1的一位)
#include<iostream>
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)队列
- 普通队列:
// hh 表示队头,tt表示队尾
int q[N], hh = 0, tt = -1;
// 向队尾插入一个数
q[ ++ tt] = x;
// 从队头弹出一个数
hh ++ ;
// 队头的值
q[hh];
// 判断队列是否为空
if (hh <= tt)
{
}
- 循环队列
// 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.基础思路
2. 01背包问题
代码:
#include<iostream>
#include<algorithm>
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.完全背包问题
#include <iostream>
#include <algorithm>
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.多重背包问题
#include <iostream>
#include <algorithm>
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.多重背包问题Ⅱ
#include <iostream>
#include <algorithm>
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.多组背包问题
#include <iostream>
#include <algorithm>
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.分解质因数
- 试除法时间复杂度 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~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; //去除该质数所有的倍数;
}
}
}
- 线性筛法
原理: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的最小质因子
}
}
}