【学习笔记】

学习笔记

  • 一、枚举模拟
    • 1.查找
      • 1)杨辉三角形(查找):
    • 2.排序
      • 1)利用sort函数排序
      • 2)快速排序
      • 2)例题:双向排序
  • 二、图论和数论
    • 1.最短路
    • 2.质因数分解
    • 3.质数判断
      • 1)厄拉多塞筛法求素数(查找范围内的所有质数)
    • 4.最大公因数
    • 5.最小公倍数
    • 6.搜索
      • DFS:所有组合等类似字眼时,我们第一感觉就要想到用dfs
      • BFS:
      • 二叉树
    • 7.注意度、邻接阵的使用
  • 三、动态规划
    • 1.01背包型
    • 2.线性递推型
    • 3.区间动态型
    • 4.状态压缩
  • 四、算法模板
    • 1.x的k进制拆分
    • 2.并查集
  • 五、小知识点
    • 1.斐波那契数列与黄金分割:
    • 2.修改数组:
    • 3.公式和函数:
    • 4.返回不正常值可能问题
    • 5.位运算
    • 6.vector
    • 7.string
    • 8.set
    • 9.手算题
    • 10.数据类型转换


一、枚举模拟

1.查找

1)杨辉三角形(查找):

杨辉三角每个值可看成C(a,b),中间对称,斜线递增,所以对斜线用*二分查找*
详解见:https://blog.csdn.net/weixin_44091134/article/details/116748883?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164920895016782246414942%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=164920895016782246414942&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-116748883.142^v5^pc_search_result_control_group,157^v4^control&utm_term=%E8%93%9D%E6%A1%A5%E6%9D%AF%E6%9D%A8%E8%BE%89%E4%B8%89%E8%A7%92&spm=1018.2226.3001.4187
#include
#include
#include
using namespace std;
long long n;

long long c(int a,int b)
{
	long long c=1;
	for(int j=1;j<=b;a--,j++)
	{
		c=c*a*1.0/j;
		if(c>n) return c;//剪枝 
	}
	return c; 
	
}
bool check(int k)
{
	long long left=2*k,right=n;//左右区间 
	long long mid;
	while(left<right)
	{
    	mid=left+right>>1;
	    if(c(mid,k)>=n) right=mid;
		else left=mid+1;
	}
	if(c(left,k)!=n)return false;
	
	cout<<(left+1)*left/2+k+1;
	return true;	
}
int main()
{
	cin>>n;
	for(int k=16;;k--)
	{
		if(check(k))
		break;
	}

	return 0;
}

2.排序

1)利用sort函数排序

排序sort(start,end,cmp)默认升序,cmp为排序规则
//改变Sort排序方式:
bool compareper(per& p1, per& p2)
{    if (p1.p_age == p2.p_age)
		return p1.p_high > p2.p_high;
	else
		return p1.p_age < p2.p_age;
}

``

2)快速排序

(1)从数列中挑出一个元素,称为 "基准"(pivot);   
(2)重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区操作;
(3)递归地把小于基准值元素的子数列和大于基准值元素的子数列排序;
 Paritition1(int A[], int low, int high) {
   int pivot = A[low];
   while (low < high) {
     while (low < high && A[high] >= pivot) 
       --high;
     A[low] = A[high];
     while (low < high && A[low] <= pivot) 
       ++low;
     A[high] = A[low]; 
   A[low] = pivot;
return low; }

 void QuickSort(int A[], int low, int high) //快排母函数
 {
   if (low < high) {
     int pivot = Paritition1(A, low, high);
     QuickSort(A, low, pivot - 1);
     QuickSort(A, pivot + 1, high);
   }
   }

2)例题:双向排序

【学习笔记】_第1张图片【学习笔记】_第2张图片

二、图论和数论

1.最短路

【学习笔记】_第3张图片

  • Floyd算法,编写简单,复杂度高,可做填空
for (int k = 1; k <= maxn; k++) {
    for (int i = 1; i <= maxn; i++) {
      for (int j = 1; j <= maxn; j++) {
        disFloyd[i][j] = disFloyd[j][i] = min(disFloyd[i][j], disFloyd[i][k] + disFloyd[k][j]);
      }
    }
 }//k为点数,ij用来遍历数组 disfloyd为邻接矩阵
  • Dijkstra算法,复杂度低,大题
void Dijkstra() {
  memset(disDijk, 0x3f, sizeof(disDijk));
  memset(vis, 0, sizeof(vis));

  disDijk[1] = 0;

  for (int i = 1; i <= 2021; i++) {
    int curMin = 0x3f3f3f3f;
    int curIndex = -1;
    
    //找到当前未被置定的最小dij[j]点,置定
    for (int j = 1; j <= 2021; j++) { 
      if (vis[j]) {
        continue;
      }
      if (curMin > disDijk[j] || curIndex == -1) {
        curMin = disDijk[j];
        curIndex = j;
      }
    }
    vis[curIndex] = true;

    //更新加入j点后的dij ,dijk[j]=min(dijk[j],dijk[置定点]+置定点到j的距离,循环置定点相连的边的数量)
    for (unsigned int j=curIndex+1; j<=curIndex+21&&j<=2021; j++) {
      disDijk[j] = min(disDijk[j], disDijk[curIndex] + u[curIndex][j]);
    }
  }
}

2.质因数分解

例题:
【学习笔记】_第4张图片
问题可以转化成,将 2021041820210418分解成三个数 a∗b∗c 的形式,有多少种拆分方法。
将 2021041820210418质因数分解成可得:2021041820210418=2∗3^3∗17∗131∗2857∗5882353 。
对于质因数2,17,131,2857,5882353 2,17,131,2857,5882353 ,每个均可以分别分配给 a,b,c 三个位置,所以总共方法数是3^5。
对于出现了3次的质数3,可以枚举出其所有的分配方式,共10种。故总方法数为:3^5 * 10 = 2430。

/// 返回x的质因数分解结果,每一个pair的第一个元素为质因数p,第二个元素为指数a。
vector<pair<int, int> > GetPrimeFactor(int x) {
  vector<pair<int, int> > ret;
  for (int i = 2; i * i <= x; i++) {
    if (x % i == 0) {
      int cnt = 0;
      while (x % i == 0) {
        cnt++;
        x /= i;
      }
      // x的质因子i出现了cnt次
      ret.push_back(make_pair(i, cnt));
    }
  }
  if (x > 1) {
    ret.push_back(make_pair(x, 1));
  }
  return ret;
}

3.质数判断

1)厄拉多塞筛法求素数(查找范围内的所有质数)

public int CountPrimes(int n)
{
    int count = 0;
    bool[] signs = new bool[n];
    for (int i = 2; i < n; i++)
    {
        //因为在 C# 中,布尔类型的默认值为 假。所以在此处用了逻辑非(!)操作符。
        if (!signs[i])
        {
            count++;
            for (int j = i + i; j < n; j += i)
            {
                //排除不是质数的数
                signs[j] = true;
            }
        }
    }
    return count;
}

4.最大公因数

简短的写法:
int gcd(int x, int y) { return (x % y) ? gcd(y, x % y) : y; }
int lcm(int x, int y) { return x / gcd(x, y) * y; }
//辗转相除法
int divisor(int a, int b) {
	int temp;
	//比较两个数的大小,值大的数为a,值小的数为b
	if (a < b) {
		temp = a;
		a = b;
		b = temp;
	}
	//求余
	while (b != 0) {
		temp = a % b;
		a = b;
		b = temp;
	}
	return a;
}

5.最小公倍数

int minb(int i,int j)
{
	int m,n;
	int minb;
	m=max(i,j);
	n=min(i,j);
	minb=m;
	while(minb%n!=0)
	{
		minb+=m;
	}
	return minb;
}

6.搜索

DFS:所有组合等类似字眼时,我们第一感觉就要想到用dfs

本质上是暴力把所有的路径都搜索出来,它运用了回溯,保存这次的位置并深入搜索,都搜索完便回溯回来,搜下一个位置,直到把所有最深位置都搜一遍(找到目的解返回或者全部遍历完返回一个事先定好的值)。要注意的一点是,搜索的时候有记录走过的位置,标记完后可能要改回来

//算法模板;
int dfs(dep)//dep表深度
{
    if(达到目的)处理return;
    else
    {
        处理;
        Dfs(dep+1);//下一深度
    }
}
//例子:
void dfs(int i,string s[],int *a,int &count){
    if(i>=11){
        if(a[0]==1&&a[1]==4&&a[2]==3&&a[3]==3){
            count++;
        }
    }else{
        for(int j=0;j<s[i].size();j++){//此处可以结合剪枝,优化时间复杂度
            if(s[i][j]=='1'){
                a[j]+=1;
                dfs(i+1,s,a,count);
                a[j]-=1;//前面操作的步骤要进行反操作撤回
            }
        }
    }
}

BFS:

它的思想是从一个被选定的点出发;然后从这个点的所有方向每次只走一步的走到底(即其中一个方向走完一步之后换下一个方向继续走);如果得不到目的解,那就返回事先定好的值,如果找到直接返回目的解。
1.设一个deque标记当前层可搜索的层,从队头取数据,取完以后删除
2.更新数据放进deque,直到deque为空 或者达到目的;
【学习笔记】_第5张图片

伪代码:(用num[i][j]表示第i行第j个位置走过没有)
因为初始起点需要走0步,我们把num数组 初始-1mem(num,-1);
左上角为起点:
	num[1][1]=0;
	1 1进入队列
			取出当前队列第一个元素(因为二维通常需要使用结构体)
			判断它是否为终点位置?breakcontinue;
			遍历该点四个方向可行方向
				if(num[mx][my]==-1)
					num[mx][my]=num[i][k]+1
				把这个点进入队列。
	结束
具体代码:
int bfs()
{
	queue<node>q;
	q.push({1,1});
	num[1][1]=0;
	while(!q.empty)
	{
		node u=q.front();q.pop();//扔掉首元素
		for(int i=0;i<4;i++)
		{
			int mx=u.x+dx[i],my=u.y+dy[i];
			if(u.x==ex&&u.y==ey) return num[u.x][u.y];//放队列之前我们已经把距离更新了,只要找到这个点绝对是最小距离了。
			if(num[mx][my]==-1&&str[mx][my]==0)//这里还需要加一个判界,我就不手写了有点累了。
			{
				num[mx][my]=num[u.x][u.y]+1;
				q.push({mx,my});
			}
		}
	}
}

二叉树

二叉树遍历:前序遍历NLR、中序遍历LNR、后序遍历LRN

例题:98验证二叉搜索树
class Solution {
public:
    long pre=LONG_MIN;
    bool isValidBST(TreeNode* root) {
    if(root==nullptr)
    return true;

    if(!isValidBST(root->left))
    return false;

    if(root->val<=pre)
    return false;

    pre=root->val;
    return isValidBST(root->right);

    }
};

7.注意度、邻接阵的使用

三、动态规划

1.01背包型

介绍见CSDN博主「HarryXxc」的原创文章原文链接:https://blog.csdn.net/harryshumxu/article/details/106427093

#include 
#include 
using namespace std;
 
 
const int N=15;
 
 
int main()
{
    int v[N]={0,8,10,6,3,7,2};
    int w[N]={0,4,6,2,2,5,1};
 
 
    int m[N][N];
    int n=6,c=12;
    memset(m,0,sizeof(m));
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=c;j++)
        {
            if(j>=w[i])
                m[i][j]=max(m[i-1][j],m[i-1][j-w[i]]+v[i]);
 
 
            else
                m[i][j]=m[i-1][j];
        }
    }
    return 0;
}

2.线性递推型

介绍见CSDN博主「小杨_小杨」的原创文章,原文链接:https://blog.csdn.net/weixin_45843077/article/details/104194693

3.区间动态型

介绍见:CSDN博主「纯木」的原创文章,原文链接:https://blog.csdn.net/u012679087/article/details/84109739
注意:破环为链
关于环和直线区间动态区别见:
https://blog.csdn.net/qq_37006625/article/details/84760039

4.状态压缩

集合问题例如:(1)子集问题,设元素无先后关系,那么共有 2 n 2^n 2n个子集;(2)排列问题,对所有元素进行全排列,共有 n ! n! n!个全排列。
  状态压缩DP的思想:集合的状态(子集或排列)用二进制表示状态,并用二进制的位运算来遍历和操作
  介绍见CSDN博主「罗勇军」的原创文章,原文链接:https://blog.csdn.net/weixin_43914593/article/details/106432695

四、算法模板

1.x的k进制拆分

// 返回x的k进制表示
vector<int> Split(int x, int k) {
  vector<int> ret;

  if (x == 0) { // 如果输入数据就是0那么需要返回一个单独的0数字
    ret.push_back(0);
    return ret;
  }
  while (x > 0) {
    ret.push_back(x % k);
    x /= k;
  }
  reverse(ret.begin(), ret.end());
  return ret;
}

2.并查集

分为并和查两部分,定义一个数组存放父节点;一般可用于分块/类

集合树:所有节点以代表节点为父节点构成的多叉树
节点的代表节点:可以理解为节点的父节点,从当前节点出发,可以向上找到的第一个节点
集合的代表节点:可以理解为根节点,意味着该集合内所有节点向上走,最终都能到达的节点
int find(int x)
{
    if(x==parent[x])  return x;
    returnparent[x]=find(parent[x]);
}
void Union(int a,int b)
{
    intpa=find(a);
    intpb=find(b);
    if(pa!=pb) parent[pa]=pb;
}

五、小知识点

1.斐波那契数列与黄金分割:

       因为趋近,所以当输入达到一定范围后,输出一致,所以可以直接输出结果

2.修改数组:

       并查集,用数组来表示关系,可以不用存储之间输出

3.公式和函数:

  • 1b=8位二进制

  • 等差数列前n项和为n(a1+an)/2

  • 等比数列:
    【学习笔记】_第6张图片

  • 如何确定直线:两点确定一条
    点是否在直线上:三点距离为a+b=c时,
    两条线是否重合:两点是否都在另一线上

  • 最小公倍数=x*y/最大公因数。

  • 互质:公约数只有1的两个自然数(最大公约数为1)

  • 可以判特例骗分

  • 浮点数判定是否相等不是直接相等,而是差值小于某数,eg.le-5

  • reverse函数用于反转在[first,last)范围内的顺序(包括first指向的元素,不包括last指向的元素),常用于数组,字符串,容器等。#include

  • pow(i,3)//求i的三次方

  • 求根号用double sqrt(double x)

  • 浮点数绝对值fabs(),整数绝对值用abs()

  • memset(u, 0x3f, sizeof(u));//u可以是二维数组,将u全赋0x3f(int类型的极值,float为0x4f

  • 用 j * j <= i 代替 j <= √i 会更好

  • for(auto c:s)//对于s中的每个字符
    for(auto &c:s);//对于s中的每个字符,c是一个引用,赋值语句将会改变s中字符的值

4.返回不正常值可能问题

3221225477 (0xC0000005): 访问越界,一般是读或写了野指针指向的内存
3221225725 (0xC00000FD): 堆栈溢出,一般是无穷递归造成的
3221225620 (0xC0000094): 除0错误,一般发生在整型数据除了0的时候

5.位运算

1)按位与&:清零(与一个各位都为零的数值相与) 取一个数中指定位(该数的对应位为1,其余位为零)
2)按位或|:将某位置一
3)异或^:对应位翻转(异或对应1)
4)取反~
5)左移<<:左移一位=*2
6)右移>>:右移一位=/2

6.vector

二维数组初始化存放数据:先建一个一维vector,在push进二维
Vector初始化时可以先用resize预设内存,即可下标访问
Vector要初始化以后才能用下标访问

7.string

string 类提供了 6 种查找函数,每种函数以不同形式的 find 命名。
这些操作全都返回 string::size_type 类型的值,以下标形式标记查找匹配所发生的位置;
或者返回一个名为 string::npos 的特殊值,说明查找没有匹配。string 类将 npos 定义为保证大于任何有效下标的值。

当 str.find(“哦”)==string::npos时则说明字符串str中不存在“哦”,
反之,str.find(“哦”)!=string::npos则说明字符串str中存在“哦”

8.set

可以利用set不重复的功能来确定不同的数量,set可以不用判断浮点型精度问题

9.手算题

介绍见:https://blog.csdn.net/weixin_43914593/article/details/115795620

10.数据类型转换

int转string

stringstream sstream;
string strResult;
int nValue = 1000;
 
// 将int类型的值放入输入流中
sstream << nValue;
// 从sstream中抽取前面插入的int类型的值,赋给string类型
sstream >> strResult;

string转int

    stringstream sstream;
    int first, second;
 
    // 插入字符串
    sstream << "456";
    // 转换为int类型
    sstream >> first;

bool转int

// 在进行多次类型转换前,必须先运行clear()
sstream.clear();
 
// 插入bool值
sstream << true;
// 转换为int类型
sstream >> second;

多个字符串拼接和stringstream类的清空

stringstream sstream;
 
// 将多个字符串放入 sstream 中
sstream << "first" << " " << "string,";
sstream << " second string";
cout << "strResult is: " << sstream.str() << endl;
 
// 清空 sstream
sstream.str("");

你可能感兴趣的:(c++)