#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
signed main(){
std::ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
return 0;
}
一、二分
1.二分查找
-
lower_bound()
: 寻找 ≥𝑥 的第一个元素的位置std::lower_bound(); -
upper_bound()
: 寻找 >𝑥 的第一个元素的位置std::upper_bound();
vector<int> arr{0, 1, 1, 1, 8, 9, 9}; vector<int>::iterator it = lower_bound(arr.begin(), arr.end(), 7); int idx = it - arr.begin(); // idx = 4
vector<int> arr{0, 1, 1, 1, 8, 9, 9}; idx = lower_bound(arr.begin(), arr.end(), 7) - arr.begin(); // 4 idx = lower_bound(arr.begin(), arr.end(), 8) - arr.begin(); // 4 idx = upper_bound(arr.begin(), arr.end(), 7) - arr.begin(); // 4 idx = upper_bound(arr.begin(), arr.end(), 8) - arr.begin(); // 5
流程
1.确定分界线l+1!=r
int l?,r=?
2.确定返回l,r
3.套模板
int l=?,r=?;
while(l+1!=r){ int mid=(l+r)>>1; if(isBlue(q[mid])){ l=mid; } else r=mid; }
bool isBlue(int x){ if(x<=5)return true; else return false; }
#include<bits/stdc++.h> //二分查找 //往左迭代更新,对应最大值最小模型 //往右最小值最大 (左闭右开) #define endl '\n' using namespace std; int a[100000005]; int main() { std::ios::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL); int n,q;cin>>n>>q; for(int i=1;i<=n;i++) { cin>>a[i]; } while(q--) { int l=0,r=n; int x;cin>>x; while(r-l>1) { int mid=l+r>>1; if(a[mid]>=x){ r=mid; } else{ l=mid; } } if(a[r]!=x) { cout<<-1<<endl; } else{ cout<<r-1<<endl; } } }
2.二分答案
二分答案介绍:
二分答案就是说用 二分 的方法枚举答案,具体请看例子:
我现在在一堆木头中找最长的,我可以用一下方法:
-
最找到可行范围内最中间的数,用 �ℎ���check函数判断他是否可行,分两种情况:
-
如果可行(check函数返回11)标记,返回 �ℎ��ℎ��(���,�)chazhao(mid,r*)
-
如果不可行(check函数返回00)标记,返回 �ℎ��ℎ��(�,���)chazhao(l,mid*)
表如下:
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|
1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 |
#include<bits/stdc++.h> #define int long long using namespace std; //二分答案——最小值最大问题,取最大向右查找 //左闭右开 //要写check函数 int n,k; int a[100000005]; bool check(int mid){ int cnt=0; for(int i=1;i<=n;i++){ cnt+=(a[i]/mid); } return cnt>=k; } signed main(){ cin>>n>>k; int sum=0; for(int i=1;i<=n;i++){ cin>>a[i]; sum+=a[i]; } int l=0,r=sum+1; while(r-l>1){ int mid=l+r>>1; if(check(mid)){ l=mid; } else{ r=mid; } } cout<<l<<endl; return 0; }
二、结构体
#include<bits/stdc++.h> using namespace std; struct node{ int id; string s; string num; int year; }a[100010];//node类型的结构体数组 bool cmp(node x,node y){//结构体排序 if(x.id!=y.id) return x.id<y.id; else return x.s<y.s; } int main(){ int n,m;cin>>n>>m; for(int i=1;i<=n;i++){ cin>>a[i].id>>a[i].s>>a[i].num>>a[i].year; } sort(a+1,a+1+n,cmp); for(int i=1;i<=n;i++){ cout<<a[i].id<<" "<<a[i].s<<' '<<a[i].num<<' '<<a[i].year<<endl; } return 0; }
三、约瑟夫问题
三、公式递推 假设: 存活者位置 = F(N,M);
例如N=6,M=5,被杀掉的顺序是:5,4,6,2,3,1。可知: 存活者位置 = F(6,5);
给出例子中求存活者位置的递推过程 总人数 = 1 : F(1,5)= 0 总人数 = 2 : F(2,5)= (F(1,5)+5)% 2 = 1 总人数 = 3 : F(3,5)= (F(2,5)+5)% 3 = 0 总人数 = 4 : F(4,5)= (F(3,5)+5)% 4 = 1 总人数 = 5 : F(5,5)= (F(4,5)+5)% 5 = 1 总人数 = 6 : F(6,5)= (F(5,5)+5)% 6 = 0
思考:为什么加5取余?
例:F(3,5)= (F(2,5)+5)% 3 = 0
此时总共3人,即[0] 、[1]、[2]三个位置 在原本只有两个人的情况下,存活位置为【1】的人需要向后移动五个单位 而要向后数五个单位,则需要以环形的结构进行遍历 ->[2]->[0]->[1]->[2]->[0] 也就是环形遍历约瑟夫环,得出最后停在[0]的位置,这也是取3的余数得出的结果。
最后我们得出公式
F(N,M)=(F(N-1,M)+M)%N
#include<iostream> #include<vector> #include<algorithm> using namespace std; int main() { //不会 int n,k,r=0; cin>>n>>k; for(int i=1; i<=n; i++) { r = (r+k)%i; } cout<<r+1; return 0; }
四、判断闰年.
excel只能计算1900年之后的,看网上都说1000年闰年平年为一个循环,可以加1000年之后再映射。
if(n%4==0&&n%100!=0||n%400==0)//特判闰年为29天 正常平年为28天
输入年份和月份,输出这一年的这一月有多少天。需要考虑闰年。
输入格式
输入两个正整数,分别表示年份 �y 和月数 �m,以空格隔开。
输出格式
输出一行一个正整数,表示这个月有多少天。
输入 #1复制
1926 8
输出 #1复制
31
#include<bits/stdc++.h> using namespace std; int main() { int y,m; cin>>y>>m; int day[13]={0,31,28,31,30,31,30,31,31,30,31,30,31}; if(y%4==0&&y%100!=0||y%400==0) { day[2]=29; } cout<<day[m]; return 0; }
五、位运算
使用位运算来进行大幅度累加,是倍增的思想
i<<1 等同于 i*2 i>>1 等同于 i/2
六、三角形
Solution
这道题目看上去不难,做起来却需要多加思考。
首先,需要对三条边的长度进行排序,用一个d数组存储后sort一遍即可。
排序完之后看,如果最长的一条边大于等于另外两条边的长度和,则无法构成三角形。等腰三角形、等边三角形和直角三角形这些也都好判断,这里不再赘述。
最难的是锐角三角形和钝角三角形。可能大多数小学生和部分初中生都不知道如何判定锐角和钝角三角形。那首先把结论放在这里:
在一个三角形中,如果较短的两条边的平方和大于最长边的平方,那么这个三角形是锐角三角形,否则它是钝角三角形。
可能大多数题解都不会涉及这个结论的证明,那下面我来为大家证明一下。这个结论的证明需要涉及到余弦定理。
我们都知道,设三角形三个角的大小分别为∠A,∠B,∠C,三个角的对边分别为a,b,c*,则有如下结论:
$$
a ^2 =b ^2 +c ^2 −2bccosA
$$
$$
∴b ^2 +c ^2 −a ^2 =2bccosA
$$
这就是余弦定理。
我们又知道:
P5717 【深基3.习8】三角形分类 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
总结
要做出这题,你必须知道以下几点:
-
三角形内任意22边之和大于第33边
-
等边三角形33条边相等,等腰三角形两腰相等。
关于勾股定理:
假设�a为最长边,�b为次长边,�c为最短边。
如果
$$
a^ 2 = b^ 2 + c^ 2
$$
,这个三角形是一个直角三角形。
如果
$$
a^ 2 > b^ 2 + c^ 2
$$
,这个三角形是一个钝角三角形。
如果
$$
a^ 2 < b^ 2 + c^ 2
$$
,这个三角形是一个锐角三角形。
因为勾股定理必须找出最长边,所以可以用a数组存储三条边的长度,然后从小到大排序。
明白了这几点,就可以开始写代码啦~
#include <cstdio> #include <algorithm> using namespace std; int a, b, c; int main() { scanf("%d%d%d", &a, &b, &c); int d[4] = {0, a, b, c}; sort(d + 1, d + 4); if(d[1] + d[2] <= d[3]) { printf("Not triangle\n"); return 0; } if(d[1] * d[1] + d[2] * d[2] == d[3] * d[3]) printf("Right triangle\n"); else if(d[1] * d[1] + d[2] * d[2] > d[3] * d[3]) printf("Acute triangle\n"); else if(d[1] * d[1] + d[2] * d[2] < d[3] * d[3]) printf("Obtuse triangle\n"); if(a == b || b == c || a == c) printf("Isosceles triangle\n"); if(a == b && b == c) printf("Equilateral triangle\n"); return 0; }
七、Dev-C++怎样编译支持C++11
1.打开Dev-C++,在菜单栏上选择【工具[T]】-【编译选项[C]】
2.2.在打开的窗口中,将【编译时加入一下命令】选中√,并加入以下语句
八、前缀和差分
前缀和:积分
差分:微分来理解
1.一维前缀和
sum[i]=sum[i-1]+arr[i],i>0;
sum[0]=arr[0],i>0
sum[i]->为[0,i]的区间和;
sum[L,R]=sum[R]-sum[L-1] ,L>0
=sum[R] L=0;
#include<bits/stdc++.h> using namespace std; #define get_sum(L,R) (L?sum[R]-sum[L-1]:sum[R]) //如果L!=0定义函数 int main() { const int n=5; int arr[n]={1,3,7,5,6}; int sum[n]; sum[0]=arr[0]; for(int i=1;i<n;i++) sum[i]=sum[i-1]+arr[i]; cout<<get_sum(2,4)<<endl; return 0; } //const int N=1e5+10;
多次查询(消息TLE)
#include<iostream> #include<bits/stdc++.h> using namespace std; #define get_sum(L,R) (L?sum[R]-sum[L-1]:sum[R]) int arr[100050]; int sum[100050]; int main() { int n;cin>>n; for(int i=1;i<=n;i++) { cin>>arr[i]; } sum[1]=arr[1]; for(int i=2;i<n+1;i++) sum[i]=sum[i-1]+arr[i]; int m;cin>>m; while(m--) { int l,r;cin>>l>>r; cout<<get_sum(l,r)<<endl; } return 0; }
二维前缀和
二位前缀和顾名思义就是在二维数组里的前缀和
例如 存在一个二维数组
$$
a[10] [10]
$$
我们想要求a3的前缀和 应该怎么求呢?
由图可知 应该是该点左上部分的面积
我们可以用数学知识来解决这个问题
1.求出该点左区域的面积 2.求出该点上区域的面积 3.加上该点的原数组的点的数值 4.减去左上部分多加的元素 最后可以得出公式 如果要求该点的前缀和 那么就可以用公式
例题
这道题需要我们求出一块矩形区域的和
例如这块红色矩形的和 我们定义左上角为(x1,y1)右下角为(x2,y2)
我们可以使用上面类似的方法
-
求出
$$
b[x2][y2]
$$ -
减去
$$
b[x2][y1-1]
$$ -
减去
$$
b[x1-1][y1]
$$ -
加上多减去的部分
$$
b[x1-1][y1-1]
$$#include <iostream> using namespace std; const int N = 1010; int n, m, q; int 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", &s[i][j]);//输入原数组 for (int i = 1; i <= n; i ++ ) for (int j = 1; j <= m; j ++ ) s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];//用刚刚我们列出的公式求前缀和二维数组 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; }
2.一维差分
1.输入a
2.求d
3.修改
4.d—和—》a—和—》p(前缀和数组)
差分性质(差分标记+前缀和)
[L,R]+v == d[L]+v,d[R+1]-v; 先做差分,让后利用前缀和求出差分数组
重点:
1.把标记完成后的差分数组进行一个前缀和
2.适用于多次操作,单个询问eg.m次操作,单个询问
#include<iostream> using namespace std; int d[6]={0}; void add(int l,int r,int v)//差分标记 { d[l]+=v; d[r+1]-=v; } int main() { int arr[5]={1,3,7,5,2}; add(2,4,5);//LRV add(1,3,2); add(0,2,-3); for(int i=1;i<5;i++)d[i]+=d[i-1];//前缀和 for(int i=0;i<5;i++) {#include<iostream> #include<bits/stdc++.h> using namespace std; int d[6]={0}; void add(int l,int r,int v)//差分标记 { d[l]+=v; d[r+1]-=v; } int main() { int arr[5]={1,3,7,5,2}; add(2,4,5);//LRV add(1,3,2); add(0,2,-3); for(int i=1;i<5;i++)d[i]+=d[i-1];//前缀和 for(int i=0;i<5;i++) { arr[i]+=d[i];//因为d为最终操作 cout<<arr[i]<<" "; }//映射回原来数组 memset(d,0,sizeof(0));//因为不确定询问次数, //所以每次前缀和完都要进将d数组置为0 return 0; } arr[i]+=d[i]; }//映射回原来数组 memset(d,0,sizeof(0));//因为不确 定询问次数, //所以每次前缀和完都要进将d数组置为0 return 0; }
二维差分
#include<iostream> using namespace std; const int N = 1010; int a[N][N],b[N][N]; int m , n, q; 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]);//构造差分数组b } } 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][j - 1] + b[i - 1][j] + b[i][j] - b[i - 1][j - 1]; printf("%d ",b[i][j]); } printf("\n"); } return 0; }
九、DFS
3种体型
1.递归实现指数型枚举:两两之间选择没有关系只有0-1 总的方案数为2^n(指数型)
2.递归实现排列型枚举:下一个数要看上一个数选了没,选了就不能再选
3.递归实现组合型枚举:要是按字典序排序,要保证下一个数比上一个数大
1.指数型枚举
从 1∼n 这 n个整数中随机选取任意多个,输出所有可能的选择方案。
输入格式
输入一个整数 n。
输出格式
每行输出一种方案。
同一行内的数必须升序排列,相邻两个数用恰好 11 个空格隔开。
对于没有选任何数的方案,输出空行。
本题有自定义校验器(SPJ),各行(不同方案)之间的顺序任意。
数据范围
1≤n≤15
输入样例:
3
输出样例:
3 2 2 3 1 1 3 1 2 1 2 3
#include<bits/stdc++.h> using namespace std; const int N=20; int n; int st[N];//记录每个数的状态、0表示还没考虑,1表示选这个数,2表示不选这个数 void dfs(int x)//x表示当前枚举到的位置 { if(x>n) { for(int i=1;i<=n;i++) { if(st[i]==1) { cout<<i<<' '; } } cout<<endl; return ; } //选 st[x]=1; dfs(x+1); st[x]=0;//恢复现场 //不选 st[x]=2; dfs(x+1); st[x]=0; } int main() { cin>>n; dfs(1); return 0; }
2.递归实现排列型枚举
题目描述
按照字典序输出自然数 11到 n 所有不重复的排列,即 n*的全排列,要求所产生的任一数字序列中不允许出现重复的数字。
输入格式
一个整数 n*。
输出格式
由 1∼1∼n 组成的所有不重复的数字序列,每行一个序列。
每个数字保留 55 个场宽。
输入输出样例
输入 #1复制
3
输出 #1复制
1 2 3 1 3 2 2 1 3 2 3 1 3 1 2 3 2 1
#include<bits/stdc++.h> using namespace std; const int N=20; int n; int arr[N]; bool st[N]; void dfs(int x){//0,1 if(x>n){ for(int i=1;i<=n;i++) { cout<<" "<<arr[i]; } cout<<endl; return ; } for(int i=1;i<=n;i++){ if(!st[i]) { st[i]=true; arr[x]=i; dfs(x+1); st[i]=false; arr[x]=0; } } } int main() { cin>>n; dfs(1); return 0; }
(2)分析:
想要用DFS,我们首先要有逻辑地画出一张地图,有了地图才能去搜。
#include<iostream> using namespace std; const int N=10; int ans[N]; bool mark[N]; int n; void dfs(int u) { //"回头"的条件 if(u==n) { for(int i=0;i<n;i++)cout<<ans[i]<<" "; puts(""); return; } for(int i=1;i<=n;i++) { if(mark[i]==false) { mark[i]=true; ans[u]=i; dfs(u+1); //复原 mark[i]=false; ans[u]=0; } } } int main() { cin>>n; dfs(0); return 0; }
3.递归实现组合型枚举
题目描述
排列与组合是常用的数学方法,其中组合就是从 �n 个元素中抽出 �r 个元素(不分顺序且 �≤�r≤n),我们可以简单地将 �n 个元素理解为自然数 1,2,…,�1,2,…,n,从中任取 �r 个数。
现要求你输出所有组合。
例如 n=5,r=3r所有组合为:
123,124,125,134,135,145,234,235,245,345123,124,125,134,135,145,234,235,245,345。
输入格式
一行两个自然数 n,r(1<n<21,0≤r≤n)n,r(1<n<21,0≤r≤n)。
输出格式
所有的组合,每一个组合占一行且其中的元素按由小到大的顺序排列,每个元素占三个字符的位置,所有的组合也按字典顺序。
注意哦!输出时,每个数字需要 33 个场宽。以 C++ 为例,你可以使用下列代码:
cout << setw(3) << x;
输出占 33 个场宽的数 x。注意你需要头文件 iomanip
。
输入输出样例
输入 #1复制
5 3
输出 #1复制
1 2 3 1 2 4 1 2 5 1 3 4 1 3 5 1 4 5 2 3 4 2 3 5 2 4 5 3 4 5
#include<bits/stdc++.h> using namespace std; int n,r; const int N=20; int arr[N]; bool st[N]; void dfs(int x,int start){ if(x>r) { for(int i=1;i<=r;i++){ cout<<setw(3)<<arr[i]; } cout<<endl; return ; } for(int i=start;i<=n;i++){ arr[x]=i; dfs(x+1,i+1); arr[x]=0; } } int main() { cin>>n>>r; dfs(1,1); return 0; }
4.总结DFS回溯:
1.当前位置打标记
2.进入下一个位置
3.回溯删除标记
mark[i]=true; ans[u]=i; dfs(u+1);//下一层状态 //复原 mark[i]=false; ans[u]=0;
DFS剪枝
if((x-1)+n-start+1<=k) return ;
题目描述
已知 �n 个整数 �1,�2,⋯ ,��x1,x2,⋯,x**n,以及 11 个整数 �k(�<�k<n)。从 �n 个整数中任选 �k 个整数相加,可分别得到一系列的和。例如当 �=4n=4,�=3k=3,44 个整数分别为 3,7,12,193,7,12,19 时,可得全部的组合与它们的和为:
3+7+12=223+7+12=22
3+7+19=293+7+19=29
7+12+19=387+12+19=38
3+12+19=343+12+19=34
现在,要求你计算出和为素数共有多少种。
例如上例,只有一种的和为素数:3+7+19=293+7+19=29。
输入格式
第一行两个空格隔开的整数 �,�n,k(1≤�≤201≤n≤20,�<�k<n)。
第二行 �n 个整数,分别为 �1,�2,⋯ ,��x1,x2,⋯,x**n(1≤��≤5×1061≤x**i≤5×106)。
输出格式
输出一个整数,表示种类数。
输入输出样例
输入 #1复制
4 3 3 7 12 19
输出 #1复制
1
#include<bits/stdc++.h> using namespace std; const int N=30; int n,k; int q[N]; int arr[N];//存的是答案,也就是123/321 int res=0;//村答案 //判断素数 bool is_prim(int sum){ if(sum<2) return false; for(int i=2;i<=sum/i;i++){ if(sum%i==0) return false; } return true; } void dfs(int x,int start){//start从几开始枚举 if((x-1)+n-start+1<=k)//剪枝 x-1 + 可选的数 { return ; } if(x>k){ int sum=0; for(int i=1;i<=k;i++){ sum+=arr[i]; } if(is_prim(sum)){ res++; } return ; } for(int i=start;i<=n;i++){ arr[x]=q[i]; dfs(x+1,i+1); arr[x]=0; } } int main() { cin>>n>>k; for(int i=1;i<=n;i++) { cin>>q[i]; } dfs(1,1); cout<<res; return 0; }
5.dp(全局最优解)//贪心,局部最优解
求最优子问题 dfs(x)=max(dfs(x+1,dfs(x+2)));
求子问题的和 dfs(x)=dfs(x+1)+dfs(x+2);
dp 记忆化搜不了就用递归at 递归和递推是相反的
动态规划入门思路: dfs暴力 --> 记忆化搜索 --> 递推
1dfs > 2记忆化搜索 > 3逆序递推 > 4顺序递推 > 5优化空间 !
十、判断素数和回文数
1)素数
bool is_prim(int sum){ if(sum<2) return false; for(int i=2;i<=sum/i;i++){ if(sum%i==0) return false; } return true; }
2)回文数
bool isHWS(int num) { int temp=num,ans=0; while (temp!=0) {//构造分别从两边读是否相等 ans=ans*10+temp%10; temp/=10; } if (ans==num) return true; else return false; }
十一、二叉树
1.统计利用先序遍历创建的二叉树的深度
#include<bits/stdc++.h> using namespace std; const int N=110; char w[N]; int l[N],r[N],d[N]; int idx=0; int creat(int n,int ds){ cin>>w[n]; d[n]=ds; if(w[n]=='#') return -1; l[n]=creat(++idx,ds+1); r[n]=creat(++idx,ds+1); return n; } int main(){ creat(++idx,1); int mx=0; for(int i=1;i<=idx;i++){ if(w[i]!='#') mx=max(mx,d[i]); } cout<<mx; return 0; }
2.972: 统计利用先序遍历创建的二叉树的宽度
#include<bits/stdc++.h> using namespace std; const int N=110; char w[N]; int l[N],r[N]; int idx=0; int tong[N]; int creat(int n,int ds){ cin>>w[n]; if(w[n]=='#') return -1; tong[ds]++; l[n]=creat(++idx,ds+1); r[n]=creat(++idx,ds+1); return n; } int main(){ creat(++idx,1); int mx=0; for(int i=1;i<=110;i++){ if(w[i]!='#') mx=max(mx,tong[i]); } cout<<mx; return 0; }
3.输出利用二叉树存储的普通树的度
#include<bits/stdc++.h> using namespace std; const int N=100010; char w[N]; int cnt=1; void creat(int i) { cin>>w[i]; if(w[i]=='#')return; creat(i*2); creat(2*i+1); } void search(int i) { if(w[i]=='#')return; if(w[2*i]!='#')search(2*i); if(w[2*i+1]!='#') { cnt++; search(2*i+1); } } int main() { creat(1); search(1); if(w[3]!='#')cout<<"ERROR"; else cout<<cnt; return 0; } //普通树中节点的兄弟节点 ->二叉树中该节点的右节点 //普通树中节点的子节点 ->二叉树中该节点的左节点 //寻找普通树的度就是二叉树中所有节点右孩子数加一
十二、交换两个值
1.位运算
##include<bits/stdc++.h> using namespace std; int main(){//交换a,b 位运算 int a=123123,b=45646; a^=b; b^=a; a^=b; cout<<a<<' '<<b<<endl; return 0; }
2.引入中间变量
##include<bits/stdc++.h> using namespace std; int main(){//交换a,b 位运算 int a=123123,b=45646; int c=a; a=b; b=c; cout<<a<<' '<<b<<endl; return 0; }
十三、数组的排序
cout<<a[i]<<" \n"[i==n];//等价于如果i!=n则输出空格,否则输出换行
##include<bits/stdc++.h> using namespace std; int a[10005]; bool cmp(int x,int y){ return x>y;//从大到小排序 } int main(){ int n;cin>>n; for(int i=1;i<=n;i++){ cin>>a[i]; } sort(a+1,a+1+n,cmp);//默认排序从小到大 for(int i=1;i<=n;i++){ cout<<a[i]<<" \n"[i==n]; } return 0; }
十四、STL
1、map
map<键类型, 值类型, 比较器> mp
map<int, int> mp1; // int->int 的映射(键从小到大) map<int, int, greater<int>> st2; // int->int 的映射(键从大到小)
对于需要自定义比较器的情况,涉及一些初学时容易看迷糊的语法(重载小括号运算符 / lambda 表达式),在此就不展开讲了
遍历
可使用迭代器进行遍历:
for (map<int, int>::iterator it = mp.begin(); it != mp.end(); ++it) cout << it->first << ' ' << it->second << endl;
十五、数据结构
1.判断给定有向图是否存在回路:拓扑排序(删掉所有入度为1的节点进行比较)
拓扑排序是针对有向无环图(DAG)的一种排序算法。如果一个有向图无法进行拓扑排序,即在尝试拓扑排序的过程中发现无法将所有顶点放入队列,或者输出的顶点顺序中存在依赖关系,则可以判断该图中存在环。
bool topsort()//拓扑排序 { for(int i=1;i<=n;i++) { if(!d[a[i]-'A'])//如果节点数不等于0 { q.push(a[i]-'A'); } } while(!q.empty()) { int x=q.front(); q.pop(); cnt++; for(int i=1;i<=n;i++) { if(g[x][a[i]-'A']) { if(!(--d[a[i]-'A'])) { q.push(a[i]-'A'); } } } } return cnt!=n; }
十六、质因数分解
首先要知道唯一分解定理:一个数能且只能分解为一组质数的乘积。可知,若输入的数满足题目条件,他就只能分解为两个质数的乘积。所以在比他小且大于1的自然数中,只有那两个数能整除它,之间不可能再有任何合数或质数能整除它了,因为最小的能整除它的合数已经是他本身了。
题目描述
已知正整数 𝑛n 是两个不同的质数的乘积,试求出两者中较大的那个质数。
输入格式
输入一个正整数 𝑛n。
输出格式
输出一个正整数 𝑝p,即较大的那个质数。
#include<bits/stdc++.h> using namespace std; int main() { int n;cin>>n; for(int i=2;i<=n;i++) { if(n%i==0) { cout<<n/i; return 0; } } return 0; }
二十、求最大公倍数和最小公因数大公因数、最小公倍数
1.最大公倍数、最小共因数
1:算法思想 (1)先求最大公因数 辗转相除法:首先保证x>y,不然交换两个数值,x要一直保持是最大值,求余数d=x%y,判断余数是否为0,如果为0,则y是最大公因数,否则x=y,y=d;直到d为0,此时最大公因数是y (2)再求最小公倍数 最小公倍数是等于输入的两个整数的乘积再除以最大公约数
#include <iostream> using namespace std; //辗转相除法,两个数的最大公约数,指的是能同时整除他们的整数 int gcd(int a,int b) { int da=max(a,b); int xiao=min(a,b); if(da%xiao==0) return xiao; else return gcd(xiao,da%xiao); } //两个数的最小公倍数等于两个整数之积除以最大公倍数 int lcm(int a,int b) { return a*b/gcd(a,b); } int main() { int x,y; cout<<"输入这两个整数:"; cin>>x>>y; cout<<"这两个整数的最大公因数:"<<gcd(x,y)<<endl; cout<<"这两个整数的最小公倍数:"<<lcm(x,y)<<endl; }
2.最小公倍数、最大公因数
方法一:辗转相除法:
首先判断m是否小于n,不然交换两个值,始终保持m永远是最大的值,求余d=m%n,判断余数是否为0如过为0,则最大公因数为n,否则m=n;n=d;接着求余直到余数d为0,此时最大公因数为n。
方法二:相减法:
如果m,n相等,最大公因数为两个数的任何一个,否则当m>n时,m=m-n,n>m时,n=n-m,一直减到m=n时输出max等于m,n两个数任何一个。
方法三:穷举法
首先保持第一个数为最大的值否则交换两个值,令i=m,开始递减,直到m和n同时除以i为0,此时输出最大公因数为max=i。
#include<iostream> using namespace std; class Calculate//建立一个类 { private: int m; int n; int temp; //用于两数转换 int d; //余数 int max; //最大公因数 int min; //最小公倍数 int product; //m和n的乘积 public: void MAX_common1(); //最大公因数算法1 void MAX_common2(); //最大公因数算法2 void MAX_common3(); //最大公因数算法3 void MIN_multiple(); //最小公倍数 void cycle(); //选择方法循环 }; void Calculate::MAX_common1()//辗转相除法 { cout<<"请输入你需要计算的两个数:(x x)"<<endl; cin>>m>>n; product=m*n; if(m<n)//交换数值永远保持m是最大的值 { temp=m; m=n; n=temp; }; d=m%n; //求余 if(d==0) // max=n; else while(d!=0)//直到余数为0 { m=n; n=d; d=m%n; max=n; }; min=product/max; cout<<"最大公因数为:"<<max<<endl; cout<<"最小公倍数为:"<<min<<endl; } void Calculate ::MAX_common2() //相减法 { cout<<"请输入你需要计算的两个数:(x x)"<<endl; cin>>m>>n; product=m*n; while(m!=n) //大数减小数 { if(m>n) m=m-n; else n=n-m; } max=n; min=product/max; cout<<"最大公因数为:"<<max<<endl; cout<<"最小公倍数为:"<<min<<endl; } void Calculate::MAX_common3()//穷举法 { int i; cout<<"请输入你需要计算的两个数:(x x)"<<endl; cin>>m>>n; product=m*n; if(m<n) //保持m永远为最大的数 { temp=m; m=n; n=temp; }; for(i=m;i>1;i--) //从输入的最大一个数开始逐渐递减寻找一个数能被两个数同时整数的 { if(m%i==0 && n%i==0) break; } max=i; min=product/max; cout<<"最大公因数为:"<<max<<endl; cout<<"最大公倍数为:"<<min<<endl; } void cycle() { int Q; Calculate c; cout<<"1.方法一(辗转相输法)"<<endl; cout<<"2.方法二(相减法)"<<endl; cout<<"3.方法三(穷举法)"<<endl; cout<<"4. 退出本程序"<<endl; cout<<"请选择你需要使用的方法:"; cin>>Q; switch(Q) { case 1: c.MAX_common1();cycle(); break; case 2: c.MAX_common2();cycle(); break; case 3: c.MAX_common3();cycle(); break; case 4: getchar(); } } void main() { int Q; Calculate c; cout<<"1.方法一(辗转相输法)"<<endl; cout<<"2.方法二(相减法)"<<endl; cout<<"3.方法三(穷举法)"<<endl; cout<<"4. 退出本程序"<<endl; cout<<"请选择你需要使用的方法:"; cin>>Q; switch(Q) { case 1: c.MAX_common1();cycle(); break; case 2: c.MAX_common2();cycle(); break; case 3: c.MAX_common3();cycle(); break; case 4: getchar(); } }
背包
1.01背包
写出递推公式的方法
递推的公式=dfs向下递归的公式
递推数组的初始值=递归的边界
输出递归搜索树的顶部
暴力dfs
#include<bits/stdc++.h> using namespace std; const int N=10010; int n,m;//数量容积 int v[N],w[N];//体积价值 int dfs(int x,int spV){//个数,剩余的容积 if(x>n)return 0; else{ if(spV<v[x])return dfs(x+1,spV);//不选 else if(spV>=v[x]){ return max(dfs(x+1,spV),dfs(x+1,spV-v[x])+w[x]); } } } int main(){ cin>>n>>m; for(int i=1;i<=n;i++)cin>>v[i]>>w[i]; int res=dfs(1,m); cout<<res; return 0; }
1.记忆化搜索
#include<bits/stdc++.h> using namespace std; const int N=10010; int n,m;//数量容积 int v[N],w[N];//体积价值 //正序递推 mem[i][j] 存 //从前i个物品 中[1,i] 中选,选总体积<=j 的物品总价值的最大值 int mem[N][N];//优化记忆化搜索 int dfs(int x,int spV){//个数,剩余的容积 if(mem[x][spV])return mem[x][spV]; int sum=0; if(x>n)return 0; else{ if(spV<v[x])sum=dfs(x+1,spV);//不选 else if(spV>=v[x]){ sum=max(dfs(x+1,spV),dfs(x+1,spV-v[x])+w[x]); } } mem[x][spV]=sum; return sum; } int main(){ cin>>n>>m; for(int i=1;i<=n;i++)cin>>v[i]>>w[i]; int res=dfs(1,m); cout<<res; return 0; }
2.递推
#include<bits/stdc++.h> using namespace std; const int N=10010; int n,m; int v[N],w[N];// 体积,价值 int f[N][N];//递推数组 int main(){ cin>>n>>m; for(int i=1;i<=n;i++){ cin>>v[i]>>w[i];//体积价值 } //逆序ditui for(int i=n;i>=1;i--){ for(int j=0;j<=m;j++){ if(j<v[i]) f[i][j]=f[i+1][j];//if(spV<v[x])sum=dfs(x+1,spV); else if(j>=v[i]){ f[i][j]=max(f[i+1][j],f[i+1][j-v[i]]+w[i]);//sum=max(dfs(x+1,spV),dfs(x+1,spV-v[x])+w[x]); } } } cout<<f[1][m]; return 0; }
2.1递推省空间
#include<bits/stdc++.h> using namespace std; const int N=10010; int n,m; int v[N],w[N];// 体积,价值 int f[N];//递推数组 int g[N]; int mem[N][N];//正序递推 mem[i][j] 存 //从前i个物品 中[1,i] 中选,选总体积<=j 的物品总价值的最大值 int main(){ cin>>n>>m; for(int i=1;i<=n;i++){ cin>>v[i]>>w[i];//体积价值 } //逆序ditui for(int i=n;i>=1;i--){ for(int j=0;j<=m;j++){ if(j<v[i]) g[j]=f[j];//if(spV<v[x])sum=dfs(x+1,spV); else g[j]=max(f[j],f[j-v[i]]+w[i]);//sum=max(dfs(x+1,spV),dfs(x+1,spV-v[x])+w[x]); } memcpy(f,g,sizeof f); } cout<<f[m]; return 0; }
3一维数组实现01背包f
#include<bits/stdc++.h> using namespace std; const int N=10010; int n,m; int v[N],w[N];// 体积,价值 int f[N];//递推数组 int g[N]; int mem[N][N];//正序递推 mem[i][j] 存 //从前i个物品 中[1,i] 中选,选总体积<=j 的物品总价值的最大值 int main(){ cin>>n>>m; for(int i=1;i<=n;i++){ cin>>v[i]>>w[i];//体积价值 } // int res=dfs(1,m);//从第一个背包开始搜索不能超过背包的溶剂 // cout<<res;//最大价值 //逆序ditui for(int i=n;i>=1;i--){ for(int j=m;j>=0;j--){//必须逆序更新 if(j<v[i])f[j]=f[j]; else if(j>=v[i]) f[j]=max(f[j],f[j-v[i]]+w[i]); } } cout<<f[m]; return 0; }
2.二维背包
#include<bits/stdc++.h> using namespace std; const int N=10010; int n,V,M; int f[N][110][110]; int main(){ cin>>n>>V>>M; for(int i=1;i<=n;i++)cin>>v[i]>>m[i]>>w[i]; for(int i=n;i>=1;i--){ for(int j=0;j<=V;j++){ for(int k=0;k<=M;k++){ if(j<v[i]||k<m[i]){ f[i][j][k]=f[i+1][j][k]; } else f[i][j][k]=max(f[i+1][j][k],f[i+1][j-v[i]][k-m[i]]+w[i]); } } } cout<<f[1][V][M]; return 0; }
3.完全背包
#include<bits/stdc++.h> //每个物品有无限件可用 using namespace std; const int N=10010; int n,m; int v[N],w[N]; int mem[N][N]; int dfs(int x,int spV){ if(mem[x][spV])return mem[x][spV]; int sum=0; if(x>n)return 0; if(spV<v[x])sum=dfs(x+1,spV); else sum=max(dfs(x+1,spV),dfs(x,spV-v[x])+w[x]);//拿了之后再拿就没必要考虑下一个物品 mem[x][spV]=sum;//最优解 return sum; } int main(){ cin>>n>>m; for(int i=1;i<=n;i++){ cin>>v[i]>>w[i]; } int res=dfs(1,m); cout<<res; return 0; }
正序递推
#include<bits/stdc++.h> //每个物品有无限件可用 using namespace std; const int N=10010; int n,m; int v[N],w[N]; int mem[N][N]; int f[N][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=0;j<=m;j++){ if(j<v[i])f[i][j]=f[i-1][j]; else f[i][j]=max(f[i-1][j],f[i][j-v[i]]+w[i]); } } cout<<f[n][m]; return 0; }
倒序递推
#include<bits/stdc++.h> //每个物品有无限件可用 using namespace std; const int N=10010; int n,m; int v[N],w[N]; int mem[N][N]; int f[N][N]; int main(){ cin>>n>>m; for(int i=1;i<=n;i++){ cin>>v[i]>>w[i]; }//倒序枚举 for(int i=n;i>=1;i--){ for(int j=0;j<=m;j++){ if(j<v[i])f[i][j]=f[i+1][j]; else f[i][j]=max(f[i+1][j],f[i][j-v[i]]+w[i]); } } cout<<f[1][m]; return 0; }
动态规划做题步骤
-
重述问题
-
找到最后一步
-
去掉最后一步,是否能划分出子问题
-
考虑边界