文章目录
一、穷举
编程之前多思考,先分析
例题:求int范围内,每一位数的三次方之和加起来等于本身的数
要是全部暴力会T掉的,所以先考虑一下
考虑到每一位数都会是0-9,很小,9^3=729,完全跟不上位数的增加导致的数的增大,发现4000以后不可能出现答案。
循环到4000即可。
二、贪心
每一步选取局部(当前)最优解,把问题规模缩小
缺点:不能保证找到全局最优解
优点:速度快
例题:最速下降法
三、排序和查找
1、顺序查找
依次比较就好,查到了break
2、二分查找
必须是有序的才可以用,但是排序至少O(nlogn)
mid = lh+(lh-rh)/2;//不用(lh+rh)/2,防止溢出
if(lh>rh) break;//结束了
//但是我还在想要不要加等号,又是等号的问题,救命啊
效率O(log2n)
例题:判断n是否是完全平方数,不能用sqrt
3、selection sort选择排序
每次从所有(剩下)的数中找出一个最小的放在前面
O(n2)
4、bubble sort冒泡排序
扫描,每次找最大的放在后面,每次比较相邻两个元素,大的放后面,每次扫描进行之多n次交换
优化:如果某次扫描一次交换都没有,可以提前结束
O(n2)
5、merge sort归并排序
6、quick sort快速排序
任意选择一个元素k,把所有数分成两半,一半比k小一半比k大,每次分别排序这两部分。规模为1的时候停止,每次把选出来的k先塞到数组里面去。(分治)
k怎么选?如果每次都选第一个,容易被卡,数据出的坑一点就会出事。现在的常用做法是随机挑一个数……动态维护的过程中,一般已经是基本排好序了的,用随机比较快
divide的写法:从右向左开始检查。如果high的值大于k,high减1,继续往前检查,直到遇到一个小于k的值。
将小于k的这个值放入low的位置。然后从low位置开始从左向右检查,直到遇到一个大于k的值。
将low位置的值放入high位置,重复第一步,直到low和high重叠。将k放入此位置。
int divide( int a[], int low, int high)
{ int k = a[low];
do { while (low<high && a[high]>=k) - -high;
if (low < high) { a[low] = a[high]; ++low;}
while (low < high && a[low] <=k) ++low;
if (low < high) {a[high] = a[low]; - -high;}
} while (low != high);
a[low] = k;
return low;
}
void quicksort(int a[], int low, int high)
{ int mid;
if (low >= high) return;
mid = divide(a, low, high);
quicksort( a, low, mid-1);
quicksort( a, mid+1, high);
}
效率不确定的,平均是O(nlogn)
四、关于递归和递推
递归总是会停的,空间开完了就会停(系统一般会设上限)
递归和迭代:递归写出来清晰但是效率低,有时候写的不好甚至会严重地拉低效率(举例,fibonacci:return (f(n-1)+f(n+2));
);迭代是循环,写出来复杂但效率高,每次数据都存下来,是DP思想(救命
例题:输出蛇阵(转圈圈填数)
题解:把方阵分解为一圈一圈,每一轮函数填一圈。
例题:Hanoi tower,要求输出操作步骤!
题解:
void Hanoi(int n, char start, char finish, char temp){
if (n==1) cout << start << "->" << finish << '\t';
else {
Hanoi(n-1, start, temp, finish);
cout << start << "->" << finish << '\t';
Hanoi(n-1, temp, finish, start);
}
}
例题:全排列
题解:(不用dfs,不用bool flag[N]的算法是这样的)
void PermuteWithFixedPrefix(char str[ ], int k){
if (k == strlen(str))
cout<<str<<endl;
else
for (int i=k; i<strlen(str); ++i){
swap(str, k, i);
//对于每次k~n的子列里面的元素,依次确定它们为子列的排头元素
PermuteWithFixedPrefix(str,k+1);
//每次有一个新的打头元素的时候规模-1,进行递归
swap(str, k, i);//记得还原哦
}
}
//void ListPermutations(char str[])
//{
// PermuteWithFixedPrefix(str, 0);
//}
//包装函数,如果coder偷懒只想输入一个str,可以写这个包装函数帮他自己补k=0
int main(){
PermuteWithFixedPrefix(str,0);
} //写这个也可以吧其实
回溯法(dfs)
例题:八皇后问题(救命还真找到了以前的演示文件笑死我了
题目是行列和斜行都不能放两个及以上的皇后
放一下以前的代码
//#include<iostream>
//#include<cstdio>
//#include<cstring>
#include<bits/stdc++.h>
using namespace std;
const int N = 100;
int n,ans,a[N];
bool b[N],c[N+N],d[N+N]; //B列用过没有 C正斜行 D反斜行
void search(int x){
if (x>n){ //搜到第n+1表明弄完了
ans++;
cout<<ans<<":";
for(int i = 1;i<= n;i++) cout<<a[i]<<" ";
cout<<endl;
return;
}
for (int y = 1;y <= n;y++)
if (!b[y] && !c[x+y] && !d[x-y+n]) { //没用过
a[x] = y;
b[y] = c[x+y] = d[x-y+n] = true; //true表用过
search(x+1);
b[y] = c[x+y] = d[x-y+n] = false; //恢复哦
}
}
int main(){
while (cin>>n){
ans = 0;
memset(b,0,sizeof(b));
memset(c,0,sizeof(c));
memset(d,0,sizeof(d));
search(1);
}
return 0;
}
动 态 规 划
救命啊!!!!
在实际中经常会遇到一个复杂的问题不能简单地分成几个独立的子问题,如果用分治法的话会使得递归调用的次数呈指数增长。如Finonacci数列的计算,第i个Fibonacci数是前两个Fibonacci数之和。
它是基于分而治之算法。在每一阶段都将当前问题分解为多个已解决的子问题
为解决递归爆炸问题,通常先找出小问题的解,记录在一个表中,在解决大问题时不需要递归,只需要从表中取出小问题的解。
五、线性筛/欧拉筛
Luogu P3383 给定一个范围 n,有 q 个询问,每次输出第 k 小的素数
说明:
scanf printf写法,效率很高
#include<iostream>
using namespace std;
const int N = 100000000;
int q,n,cnt = 0,x;
int pri[N] = {0};
int minp[N] = {0};
//欧拉筛 pri存质数表,minp存每个数字的最小因数
int main(){
scanf("%d%d",&n,&q);
for(int i=2;i<=n;i++){
if(!p[i]) pri[++cnt] = i;
//i没有被筛掉,则i为质数
for(int j=1;j<=cnt;j++){
//枚举当前每个质数 ,筛去i*pri[j]
if(i*pri[j]>b) break;
//超出了就跳出
minp[i*pri[j]] = pri[j];
//找到i的最小因子时跳出,
//在此以前i的因子都没有出现,所以pri[j]是i*pri[j]的最小因子,给它筛掉
if(i%pri[j]==0) break;
}
}
for(int i=1;i<=q;i++){
scanf("%d",&x);
printf("%d\n",pri[x]);
}
return 0;
}
cin cout写法,加std::ios::sync_with_stdio(false)
可以达到scanf和printf的效率
#include<iostream>
using namespace std;
const int N = 100000000;
int q,n,cnt = 0,x;
int pri[N] = {0};
int minp[N] = {0};
int main(){
std::ios::sync_with_stdio(false);
cin>>n>>q;
for(int i=2;i<=n;i++){
if(!minp[i]) pri[++cnt] = i;
for(int j=1;j<=cnt;j++){
if(i*pri[j]>n) break;
minp[i*pri[j]] = pri[j];
if(i%pri[j]==0) break;
}
}
for(int i=1;i<=q;i++){
cin>>x;
cout<<pri[x]<<endl;
}
return 0;
}
六、Fibonacci的小空间写法
int seq[3]={1,1};
int n,i;
cin>>n; //n>1
for(i=2;i<=n;i++)
seq[i%3]=seq[(i-1)%3]+seq[(i-2)%3]
cout<<seq[n%3];
关于空间
“hello world”程序占了 58k
七、KMP算法
具体原理见上课PPT,在Data_Structure文件夹下面
自己写的风格和老师给的例程很不一样,修修补补好多遍
用luogu的板子题:
#include<iostream>
#include<cstring>
using namespace std;
const int N = 1000005;
char s[N],t[N];
int fail[N];
int lent;
void getfail(){
int k=-1,j=0;
fail[j] = k;
while(j<=lent){
if(t[k] == t[j] || k == -1){
k++;
j++;
fail[j] = k;
}
else{
k = fail[k];
}
}
}
void kmp(){
int i=0,j=0;
while(s[i] != '\0'){
if(s[i] == t[j] || j==-1){
i++;
j++;
}
else{
j = fail[j];
}
if(t[j] == '\0' && j!=-1) {//2号位
printf("%d\n",i-j+1);
i = i-j+1;//1号位
j = 0;
}
}
}
int main(){
scanf("%s",&s);
scanf("%s",&t);
lent = strlen(t);
getfail();
kmp();
for(int i=1;i<=lent;i++)
printf("%d ",fail[i]);
cout<<endl;
return 0;
}
本题和单纯看匹不匹配有些不同,因为要输出每一次匹配成功的位置。
自己写出的bug:
- 1号位,应该写
i-j+1
,写的是i-j+2
,因为没有注意到输出位置1-base,而数组本身0-base - 2号位,应该在所有同级的
if
和else
最后写,否则会漏掉最后恰好匹配的情况(一起配对到’\0’,然后在判断外层s[i]!='\0'
的时候给直接排除了)
八、ST表
AC代码如下:
#include<iostream>
#include<cmath>
using namespace std;
const int N = 100005;
int f[N][20] = {0};
int a[N];
int n,m,l,r;
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++)
f[i][0] = a[i];
for(int i=1;i<=log(n)/log(2);i++)
for(int j=1;j+(1<<i)-1<=n;j++){//-1,因为计数是加多了1的
f[j][i] = max(f[j][i-1],f[j+(1<<(i-1))][i-1]);
}
for(int i=1;i<=m;i++){
scanf("%d%d",&l,&r);
int tmp = log(r-l+1)/log(2);
printf("%d\n",max(f[l][tmp],f[r-(1<<tmp)+1][tmp]));//+1,也是计数记多了
}
return 0;
}