算法总结
本文最后更新于 80 天前,其中的信息可能已经有所发展或是发生改变。

一些不太理解的玩意:

1、

“>>=”右移后赋值运算符,‘<<=“左移后赋值运算符

参考 256 128 64 32 16 8 4 2 1

x>>=n 即x*(1/2^n)

.png

x<<=n 即x*(2^n)

.png

2、




1、加速流奇淫巧技:

一、

read()快读: 读入大量数据时比 scanf 快

 // 快速读入,加快读入速度
 inline int read()
 {
     int x=0,f=1;char ch=getchar();
     while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
     while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
     return x*f;
 }
 ​
 int main()  
 {  
     int t;  
     while(t=read())  //0进不入循环 会结束,但可以读0 
     cout<<t<<endl;  
 }



二、

输入输出 cin cout 加速: 但速度上 scanf, printf 还是略胜一筹,字符串输入输出时,gets()和puts()更快

 #include<bits/stdc++.h>
 using namespace std;
  
 int main(){
     ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
  
     return 0;
 }

注意事项:

1、不适用于混合输入输出:

如果你的程序在输入输出中同时使用了C++的输入输出流和C标准库的输入输出函数(如scanf和printf),则不应该使用这段代码。因为这会导致输入输出之间的不同步。

2、不能混用输入输出函数

在使用了这段代码后,应避免使用C标准库的输入输出函数(如printf和scanf),因为这些函数与输入输出流的同步已被关闭。简单来说,关闭了同步流,就不能用scanf和printf。

3、不影响程序正确性:

关闭输入输出流的同步不会影响程序的正确性,它只是为了提高程序的执行效率。因此,在一些对输入输出性能要求较高的场景下,可以考虑使用这段代码。

4、关闭了同步流(也就是使用这段代码),不能再用cout<<endl,而应该改用cout<<‘\n’

因为通常情况下,cout<<endl会输出一个换行符并刷新输出缓冲区,确保内容立即显示。但是,当使用了上述代码时,cout<<endl不再具有自动刷新缓冲区的功能。



三、

bitset:

1个字节 byte 8个 bit能分别存储8位的0/1

随机访问时,bitset更快;连续访问时,bool更快。

bitset<1000001>a;

a.reset();

常用基本操作:

a.set(): 将整个 bitset 设置成 true 。 b.set(pos,val=true): 将某一位设置成 true/false。 a.reset(): 将整个 bitset 设置成 false 。 b.reset(pos): 将某-位设置成 false。相当于 set(pos,false)。 a. flip(): 翻转每一位。(0↔1,相当于异或一个全是1的 bitset ) b.flip(pos): 翻转某一位。

count(): 返回 true 的数量。 size(): 返回 bitset 的大小。 test(pos): 它和 vector 中的 at()的作用是一样的,和[]运算符的区别就是越界检查, any(): 若存在某一位是 true 则返回 true ,否则返回 false 。 none(): 若所有位都是 false 则返回 true,否则返回 false 。 all(): C++11,若所有位都是 true 则返回 true,否则返回 false 。



四、

多维数组和多个数组:

多维数组:O(n)

多个数组:O(m*n)

例如二维和一维,二维数组使用方便,但效率不及使用多个一维数组。



五、

加类型局部变量的声明甚至会比不加的更慢

不加声明:auto 加声明:int,string,queue<pair<int,int>>……

在auto出现之前,

C++需要先推导等号右侧表达式的类型,

然后检查它与变量的类型是否可以转换(例如兼容转换、向下类型转换和自定义类型转换)。

在auto出现之后,

C++在推导出等号右侧表达式的类型之后,

直接指定给变量。

检查的过程变成了指定的过程,时间上可以认为差别不大。

转自:在C++11中,auto关键字的大量使用,会影响编译速度吗?



六、

empty()还是size()?

为何empty()比较好? 主要是他们之间的效率有一定差距:

empty对任意的容器都是常数时间 对于有点list实现,size需要线性时间

转自:C++学习——empty()还是size()_size和empty那个快-CSDN博客




2、字符串:

string → int:

<1> 十进制算术运算

 string s = "123";
 int num = 0;
 for (int i=0; i<s.size(); ++i) 
 {
     num = 10 * num + (s[i] - '0');
 }

<2> 使用标准库中的 atoi 函数 (Ascii to integer)

 string s = "123";
 int num = atoi(s.c_str());



int → string:

<1>

 int num = 123;
 string s = to_string(num);



判断字符串长度:

 size_t strlen(const char *str);



字符串逆序:

 reverse(str.begin(), str.end());



拼接:

append函数
 int main()
 {
     string str;
     string str2 = "Writing ";
     string str3 = "print 10 and then 5 more";
 ​
     // used in the same order as described above:
     str.append(str2);                          // "Writing "
     str.append(str3, 6, 3);                    // "10 "
     str.append(str3, 5);                       // "10 and then 5 more"
     str.append("dots are cool", 5);            // "dots "
     str.append("here: ");                      // "here: "
     str.append(10, '.');                       // ".........."
     str.append(str3.begin() + 8, str3.end());  // " and then 5 more"
 ​
     cout << str << '\n'; 
     return 0;
 }
正向查找find函数
s.find(str,pos)

string中find()返回值是字母在母串中的下标位置。 如果没有找到,那么会返回一个特别的标记npos,一般写作string::npos

string s, c;
int main() {
  s = "apple";
  c = "l";
  int index = s.find(c);
  if (index != string::npos)
    cout << index << endl;
} // 输出为3
s.find(str,pos)

find(str,pos)是用来寻找从pos开始(包括pos处字符)匹配str的位置。

string s, c;
int main() {
  s = "laaaal";
  c = "l";
  int index = s.find(c,3);  //从字符串s下标3的位置开始寻找
  if (index != string::npos)
  cout << index << endl;
} // 输出为5
s.find_first_of(str) 和 s.find_last_of(str)
string s, c;
int main() {
  s = "laaaal";
  c = "l";
  cout << "first index:" << s.find_first_of(c) << endl;
  cout << "last index:" << s.find_last_of(c) << endl;
}
/*
输出:
     first index:0
     last index:5
*/
查找目标字符串在字符串出现的总次数
int main()
{
    int n = 0, sum = 0;
    string s1("qwertyqwertyqwerty");
    string s2("ert");
    string temp = s1;
    while (1) 
    {               //循环:查找到子串就把子串删去
        n = temp.find(s2);    //返回子串的位置
        if (n != -1) 
        {        //n=-1表示未找到子串
            temp = temp.substr(n + s2.length(), temp.length() - s2.length());   //开一个新的字符串变量temp存储删去子串后的字符串
            sum++;            //出现次数
        }
        else break;
    }
    cout << sum;
} // 输出为3
逆向查找rfind函数:
s.rfind(str)

从字符串右侧开始匹配str,并返回在字符串中的下标位置

string s = "apple";
cout << s.rfind("l") << endl;
// 输出为3
rfind(str,pos)

从pos开始,向前查找符合条件的字符串

strsub函数:
#include<iostream>
#include<string>
using namespace std;
int main()
{
	string s="sfsa";
	string a=s.substr(0,3);
	string b=s.substr();
	string c=s.substr(2,3);
	cout<<a<<endl;
	cout<<b<<endl;
	cout<<c<<endl;
	return 0;
}
/*
输出:
	sfs
	sfsa
	sa
*/




3、求素数埃氏筛、线性筛:

素数:只能被常数1或自己整除,不能被其他整数整除的正整数。

合数:在大于1的整数中除了能被1和本身整除外,还能被其他数(0除外)整除的数。



埃氏筛:

埃氏筛核心:从2开始删去素数本身倍数,向后找到的第一个数字一定是素数。

.gif
const unsigned long long N = 1e9 + 10;
int n, i, j, k = 0;
bitset<N> a;
int main()
{
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);   /*输入输出加速*/
	a.reset();   /*初始化bitset全为0*/
	cin >> n;
	for (i = 2; i <= n; i++)
	{
		if (a[i] == 0)
		{
			for (j = 2; i * j <= n; j++)
			{
				a[i * j] = 1;
			}
			k++;    /*计算素食个数*/
			cout << i << '\n';   /*输出素数*/
		}
	}
	cout << k;  /*输出素数总个数*/
	return 0;
}



线性筛:

在实现形式上,埃氏筛是直接用素数遍乘自然数删去自身倍数;

线性筛是先建立一个素数数列,然后用每个自然数遍乘数列中的素数删去素数倍数。

算法简述:

s1:建立标记数列(从2到n的自然数)和素数数列;

s2:第一个数字是2(看标记是素数),把素数2放进素数数列,标记2×2为非素数;

s3:第二个数字是3(看标记是素数),把素数3放入素数数列,标记3×2、3×3为非素数;

s4:第三个数字是4(看标记是合数),标记4×2为非素数,break;

s5:第四个数字是5(看标记是素数),把素数5放入素数数列,标记5×2、5×3、5×5为非素数;

s6:第五个数字是6(看标记是合数),标记6×2为非素数,break;

s7:第六个数字是7(看标记是素数),把素数7放入素数数列,标记7×2、7×3、7×5、7×7为非素数;

……

const unsigned long long N = 1e8, M = 5e8;
int p[M];
bitset<N> a;
int n, i, j, k = 0;
int main()
{
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);  //输入输出加速
	cin >> n;
	a.reset();  /*初始化bitset全为0*/
	for (i = 2; i <= n; i++)
	{
		if (a[i] == 0)
		{
			p[++k] = i;
			cout << i << '\n';  /*输出素数*/
		}
		for (j = 1; i * p[j] <= n; j++)
		{
			a[i * p[j]] = 1;
			if (i % p[j] == 0)
				break;
		}
	}
	cout << k;  /*输出素数总个数*/
	return 0;
}




4、年份日期:

闰年判定:

闰年:年份能够被4整除但不能被100整除或者能被400整除的为闰年。

bool isryear(int n){
     return n%400==0||(n%4==0&&n%100!=0);
}



月份天数:

//也可以将数组第一个位置空出来,即填上一个随意的值,这样就可以将月份和数组下标对应了,方便访问
int pmonths[]={31,28,31,30,31,30,31,31,30,31,30,31};   //平年每月天数
int rmonths[]={31,29,31,30,31,30,31,31,30,31,30,31};   //闰年每年天数




5、二分:

整数二分:

// 整数二分模板:

bool check(int mid)
{
	//...;
}

// 查找满足条件的最小值(第一次出现的值)
int binarySearch1(int l, int r)
{
	int l, r;
	//l = 1, r = n;
	int mid = l + r >> 1;
	while (l < r)
	{
		if (check(mid))		/* a[mid] >= x */
			r = mid;
		else
			l = mid + 1;
	}
	return l;
}

// 查找满足条件的最大值(最后一次出现的值)
int binarySearch2(int l, int r)
{
	int l, r;
	//l = 1, r = n;
	int mid = l + r + 1 >> 1;
	while (l < r)
	{
		if (check(mid))     /* a[mid] <= x */
			l = mid;
		else
			r = mid - 1;
	}
	return l;
}

例题:

经典二分查找

#include<bits/stdc++.h>
using namespace std;

const int N = 1e6 + 10;
int q[N], n, m;
int main()
{
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
		cin >> q[i];
	while (m--)
	{
		int x;
		cin >> x;
		int l = 1, r = n;
		while (l < r)
		{
			int mid = l + r >> 1;
			if (q[mid] >= x)
				r = mid;
			else
				l = mid + 1;
		}
		if (q[l] != x)
			cout << -1 << " ";
		else
			cout << l << " ";
	}
	return 0;
}



浮点二分:

bool check(double x) 
{/* ... */} // 检查x是否满足某种性质

double binarySearch3(double l, double r)
{
    const double eps = 1e-6;   // eps 表示精度, 假如要精确至小数点后三位,则差值要求小于1e-5,以此类推......
    while (r - l > eps)
    {
        double mid = (l + r) / 2;
        if (check(mid)) 
            r = mid;
        else 
            l = mid;
    }
    return l;
}

例题:

有一个整数x,试求出√x,保留两位小数

#include<bits/stdc++.h>
using namespace std;

int main()
{
	double x;
	cin >> x;
	double l = 0, r = x;
	while (r - l > 1e-4)
	{
		double mid = (l + r) / 2;
		if (mid * mid > x)
			r = mid;
		else
			l = mid;
	}
	cout << setprecision(2) << fixed << l << endl;
	return 0;
}



关于lower_bound()和upper_bound()的常见用法:

lower_bound( )和upper_bound( )都是利用二分查找的方法在一个排好序的数组中进行查找的。

在从小到大的排序数组中,

lower_bound( begin,end,num):

从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。

upper_bound( begin,end,num):

从数组的begin位置到end-1位置二分查找第一个大于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。

在从大到小的排序数组中,重载lower_bound()和upper_bound()

lower_bound( begin,end,num,greater<type>() ):

从数组的begin位置到end-1位置二分查找第一个小于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。

upper_bound( begin,end,num,greater<type>() ):

从数组的begin位置到end-1位置二分查找第一个小于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。




6、排序:

一般直接用sort,sort省事且基本和下面算法速度差不多

.png



快速排序:

模板:

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);
}



归并排序:

.png

模板:

const int N = 1e5 + 10;
int q[N], tmp[N], n;
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];
}




7、高精度加减乘除算法:

高精度加法:

//加法
vector<int> add(vector<int> &A, vector<int> &B)
{
	vector<int>C; //存放结果
	int t = 0; //进位
	for (int i = 0; i < A.size() || i < B.size(); i++)
	{
		if (i < A.size())
			t += A[i];
		if (i < B.size())
			t += B[i];
		C.push_back(t % 10);
		t /= 10; //更新进位
	}
	if (t)
		C.push_back(1);
	return C;
}
int main()
{
	string a, b;
	vector<int> A, B;
	cin >> a >> b;
	for (int i = a.size() - 1; i >= 0; i--)  //逆序是为了便于从个位开始处理
		A.push_back(a[i] - '0'); //字符转换为数值
	for (int j = b.size() - 1; j >= 0; j--)
		B.push_back(b[j] - '0'); //字符转换为数值
	auto C = add(A, B);
	for (int i = C.size() - 1; i >= 0; i--)
		cout << C[i];
	return 0;
}



高精度减法:

//减法
bool cmp(vector<int> &A, vector<int> &B)
{
	if (A.size() != B.size())
		return A.size() > B.size();
	for (int i = 0; i < A.size(); i++)
		if (A[i] != B[i])
			return A[i] > B[i];
	return true;
}

vector<int> sub(vector<int> &A, vector<int> &B)
{
	vector<int> C;

	for (int i = 0, t = 0; i < A.size(); i++)
	{
		t = t - A[i];
		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;
}

int main()
{
	string a, b;
	vector<int> A, B;
	cin >> a >> b;

	for (int i = a.size() - 1; i >= 0; i--)
		A.push_back(a[i] - '0');
	for (int j = b.size() - 1; j >= 0; j--)
		B.push_back(b[j] - '0');

	vector<int> C;

	if (cmp(A, B))  //判断A,B大小
	{
		C = sub(A, B);
	}
	else
	{
		C = sub(B,A);
		cout << "-"; //相减为负数,要加负号
	}
	for (int i = C.size() - 1; i >= 0; i--)
		cout << C[i];
	return 0;
}

高精度乘法:

// 高精 * 低精
vector<int> mul(vector<int> &A, int &b)
{
	vector<int> C;
	int t = 0;
	for (int i = 0; i < A.size(); i++)
	{
		t += A[i] * b;
		C.push_back(t % 10);
		t /= 10;
	}
	while (t)
	{
		C.push_back(t % 10);
		t /= 10;
	}
	while (C.size() > 1 && C.back() == 0)
		C.pop_back();
	return C;
}

//高精度 * 高精度
vector<int> mul(vector<int>& A, vector<int>& B) 
{
    vector<int> C(A.size() + B.size() + 7, 0);  // 初始化为 0,C的size可以大一点

    for (int i = 0; i < A.size(); i++)
        for (int j = 0; j < B.size(); j++)
            C[i + j] += A[i] * B[j];

    int t = 0;
    for (int i = 0; i < C.size(); i++) 
    {   // i = C.size() - 1时 t 一定小于 10
        t += C[i];
        C[i] = t % 10;
        t /= 10;
    }

    while (C.size() > 1 && C.back() == 0) 
        C.pop_back();  // 必须要去前导 0,因为最高位很可能是 0
    return C;
}
.png



高精度除法:

// 高精度 ÷ 低精度
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.back() == 0 && C.size() > 1)
		C.pop_back();
	return C;
}

// 高精度 ÷ 高精度
bool cmp(vector<int>& A, vector<int>& B) 
{
    if (A.size() != B.size()) 
        return A.size() > B.size();
    for (int i = A.size() - 1; i >= 0; i--) 
    {
        if (A[i] != B[i]) 
            return A[i] > B[i];
    }
    return true;
}

vector<int> sub(vector<int>& A, vector<int>& B) 
{
    vector<int> C;
    int t = 0;
    for (int i = 0; i < A.size() || t; 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;
}

vector<int> div(vector<int>& A, vector<int>& B, vector<int>& r) 
{
    vector<int> C;
    if (!cmp(A, B)) 
    {
        C.push_back(0);
        r = A;
        return C;
    }
    int j = B.size();
    r.assign(A.end() - j, A.end());
    while (j <= A.size()) 
    {
        int k = 0;
        while (cmp(r, B)) 
        {
            vector<int> s = sub(r, B);
            r.clear();
            r.assign(s.begin(), s.end());
            k++;
        }
        C.push_back(k);
        if (j < A.size()) 
            r.insert(r.begin(), A[A.size() - j - 1]);
        if (r.size() > 1 && r.back() == 0) 
            r.pop_back();
        j++;
    }
    reverse(C.begin(), C.end());
    while (C.size() > 1 && C.back() == 0) 
        C.pop_back();
    return C;
}

int main() 
{
    string a, b;
    cin >> a >> b;
    vector<int> A, B, r;
    for (int i = a.size() - 1; i >= 0; i--)
        A.push_back(a[i] - '0');
    for (int i = b.size() - 1; i >= 0; i--) 
        B.push_back(b[i] - '0');

    auto C = div(A, B, r);
    for (int i = C.size() - 1; i >= 0; i--) 
        printf("%d", C[i]);
    printf("\n");
    for (int i = r.size() - 1; i >= 0; i--) 
        printf("%d", r[i]);
    return 0;
}




8、前缀和:

一维前缀和:

// 一维前缀和模板
const long long N = 1e5 + 10;
long long n, m, a[N], sum[N];
int main() 
{
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
	{
		cin >> a[i];
		sum[i] = sum[i - 1] + a[i];
	}
	while (m--) 
	{
		long long l, r;
		cin >> l >> r;
		cout << sum[r] - sum[l - 1] << endl;
	}
	return 0;
}



二维前缀和:

.png

(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和为:

// S[i, j] = 第i行j列格子左上部分所有元素的和
// 以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和为:
S[x2, y2] - S[x1 - 1, y2] - S[x2, y1 - 1] + S[x1 - 1, y1 - 1]

模板:

const int N = 1e3 + 10;
int n, m, q, a[N][N], sum[N][N];
int main() 
{
    cin >> n >> m >> q;
    for (int i = 1; i <= n; i++) 
    {
        for (int j = 1; j <= m; j++) 
        {
            cin >> a[i][j];
            sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + a[i][j];
        }
    }
    while (q--) 
    {
        int x1, y1, x2, y2;
        cin >> x1 >> y1 >> x2 >> y2;
        cout << sum[x2][y2] - sum[x1 - 1][y2] - sum[x2][y1 - 1] + sum[x1 - 1][y1 - 1] << endl;
    }
    return 0;
}




9、尺取法(双指针):

反向扫描:

模板:

// 用 while 实现
int i = 0, j = n - 1;
while (i < j)
{
	...
	i++;
	j--;
}
// 用 for 实现
for (int i =, j = n - 1; i < j; i++, j--)
{
	...
}



同向扫描(快慢指针):

1、寻找区间和(尺取法产生滑动窗口)

例:给定一个长度为 n 的数组 a[ ] 和一个数 s ,在这个数组中寻找一个区间,使这个区间的数组元素之和等于 s 。输出区间的起点和终点位置。

说明:输入样例的第1行是 n ,第2行是数组 a[ ] ,第3行是区间和 s 。输出样例换行分开每种情况。

const int N = 1e5 + 10;
int a[N];

void find(int q[], int n, int s)
{
	int i = 0, j = 0, sum = q[0];
	while (j < n)  // 下面代码中保证 i <= j
	{
		if (sum >= s)
		{
			if (sum == s)
				cout << i << " " << j << endl;
			sum -= q[i];
			i++;
			if (i > j)   // 防止 i 超过 j
			{
				sum = q[i];
				j++;
			}
		}
		if(sum < s)
		{
			j++;
			sum += q[j];
		}
	}
}

int main()
{
	int n, s;
	cin >> n;
	for (int i = 0; i < n; i++)
		cin >> a[i];
	cin >> s;
	find(a, n, s);
	return 0;
}

2、数组去重:哈希和尺取法

3、多指针:

例:P1102 A-B 数对

const int N = 2e5 + 10;
int a[N], n, c;
int main()
{
	cin >> n >> c;
	for (int i = 1; i <= n; i++)
		cin >> a[i];
	long long ans = 0;
	sort(a + 1, a + 1 + n);
	for (int i = 1, j = 1, k = 1; i <= n; i++)  // 三指针
	{
		while (j <= n && a[j] - a[i] < c)
			j++;
		while (k <= n && a[k] - a[i] <= c)
			k++;
		if (a[j] - a[i] == c && a[k - 1] - a[i] == c && k >= 2)
			ans += k - j;
	}
	cout << ans;
	return 0;
}




10、快速幂:

模板:

// 递归快速幂:
typedef long long ll;
ll fastPow(ll a, ll n, ll m)   // (a^n) mod m
{
	if (n == 0)				 // 特判 a = 0, a = 1
		return 1;
	if (n == 1)
		return a;
	ll temp = fastPow(a, n >> 1, m);   // 分治
	if (n & 1)
		return (temp * temp % m * a % m) % m;  // 奇数个 a(不这么写会爆 long long)
	else
		return temp * temp % m;			      // 偶数个 a
}

// 非递归快速幂:
ll fastPow(ll a, ll n, ll m)
{
    ll temp = 1;
    while(n)
    {
        if(n & 1)      // 如果 n 为奇数
            temp *= a;  // ans 乘上当前的 a 
        a *= a;        // a 自乘
        n >>= 1;       // n 除以 2
    }
    return temp % m;
}

int main()
{
	ll a, n, m;
	cin >> a >> n >> m;
	cout << fastPow(a, n, m);
	return 0;
}




11、DFS(深搜)和BFS(宽搜):

DFS(暴搜)搜索路径:

dfs.gif

BFS搜索路径及两者比较:

dfsbfs.png

DFS:

dfs在每次回溯时都要“恢复现场”,同时可以根据题目要求,每走一步判断是否满足条件,以此来减少搜索的数据量(剪枝)

DFS.png

DFS,n皇后问题:

AcWing 843. n-皇后问题 —— 主副斜线的“秘密”




11、最大公约数(GCD)(辗转相除法)、最小公倍数(LCM):

求最大公约数方法:

.png



最大公约数:

int gcd(int m, int n) 
{
    return n ? gcd(n, m % n) : m;
}



两个数的乘积等于这两个数的最大公约数与最小公倍数的乘积

即:假设x和y的最大公约数是m,最小公倍数是n,则xy=mn.




12、动态规划:

······背包问题:

原题链接:0/1背包问题

// 朴素做法:
/* 

情况一:
第 i 个物品的体积比容量 j 还大,不能装进容量 j 的背包
那么直接继承前 i-1 个物品装进容量 j 的背包的情况即可: 即 dp[i][j] = dp[i-1][j];

情况二:
第 i 个物品的体积比容量 j 还小,能装进背包,又可以分两种情况:装/不装第 i 个物品。

① 装第 i 个物品。
从前 i-1 个物品的情况推广而来,前 i-1 个物品的价值为 dp[i-1][j]。
第 i 个物品装进背包后,背包容量减少 c[i],价值增加 w[i],有 dp[i][j] = dp[i-1][j-c[i]] + w[i]。

② 不装第 i 个物品,有 dp[i][j] = dp[i-1][j];

取两种情况最大值,状态转移方程为:
dp[i][j] = max(dp[i-1][j],dp[i-1][j-c[i]] + w[i])

*/
const int N = 1010;
int dp[N][N];
int v[N], w[N];
int n, m;
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    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 = 1; j <= m; j++)
        {
            dp[i][j] = dp[i - 1][j];
            if (j >= v[i])
                dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i]] + w[i]);
        }
    cout << dp[n][m] << '\n';
}

可以优化吗?答案是肯定的:

优化版(交替滚动):

int dp[2][N];   // 替换 int dp[][];
int solve(int n ,int m)
{
    int now = 0, old = 1; // now指向当前正在计算的一行,old指向旧的一行
    for (int i = 1; i <= n; i++)
    {
        swap(old, now); // 交替滚动,now始终指向最新的一行
        for (int j = 0; j <= m; j++) // j 循环反过来也行
        {
            if (v[i] > j)
                dp[now][j] = dp[old][j];
            else
                dp[now][j] = max(dp[old][j], dp[old][j - v[i]] + w[i]);
        }
    }
    return dp[now][m]; // 返回最新的一行
}

进一步优化,只用一维数组(自我滚动):0/1背包优化问题

int solve(int n,int m)
{
    for (int i = 1; i <= n; i++)
    {
        for (int j = m; j >= v[i]; j--) // j 循环只能反过来循环
        {
            dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
        }
    }

    return dp[m];
}

多重背包问题II(二进制优化)

//多重背包问题

/*

  二进制优化(下方举例说明):
① 假定物品件数 s = 200
② 二进制形式 k[n] = {1,2,4,8,16,32,64,73}  其所有值相加是 <=200  可以凑出[0~200]的所有数
③ 除开最后一个数不是二进制形式,73 = 200-(1+2+4+8+16+31+64)
④ 其余数,若定 64,其中 64+[0~63]->[0~127]
⑤ 最后一个数 73,其中 73+[0~127]->[0~200] 

*/

const int N = 11010, M = 2010;   // 此处1000 * log2000 , 向上取整数 N 得11000

int n, m;                        // n 表示物品总数,m 表示背包容积
int v[N], w[N];                  // v[i] 表示第i件物品的体积,w[i] 表示第 i 件物品的价值
int dp[M];                       // 组合方案中的最大价值

int main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> n >> m;
    int cnt = 0;                 // 用来存储所有新的物品

    for (int i = 1; i <= n; i++)
    {
        int a, b, s;
        cin >> a >> b >> s;     // 输入当前物品的体积,价值,个数
        int k = 1;              // 此处二进制优化,从 1 开始分(假定选择当前物品的总数为 k )
        for(int k = 1; k <= s; k <<= 1)  // 二进制,乘以 2 继续循环
        {
            v[++cnt] = a * k;   // 编号增加,体积 * 物品总数
            w[cnt] = b * k;     
            s -= k;             // 从总个数 s 中减去 k 个
        }
        if (s > 0)              // 若总个数 s 还有剩余
        {
            v[++cnt] = a * s;   // 编号增加,体积 * 剩余个数
            w[cnt] = b * s;
        }
    }

    n = cnt;                    // 将 n 更新为 cnt

    for (int i = 1; i <= n; i++)
        for (int j = m; j >= v[i]; j--)               
            // 此处优化成一维,次数使用倒序,因为原 dp[i][j] = max(dp[i][j],dp[i-1][j-v[i]*k]+w[i]*k)
            dp[j] = max(dp[j], dp[j - v[i]] + w[i]);  // 其中 v[i] 和 w[i] 已经是选定个数 k 后更新的体积和价值

    cout << dp[m] << '\n';

    return 0;
}

这里采用的优化思维主要是任意一个数都可以利用二进制拆分组合,比如 10=1+2+4+3,7=1+2+4

可能你会想,既然按照这样的数目把原来的物品重新打包,那会不会有遗漏的情况,但其实并不会。

以 7 = 1+2+4 为例,1,2,4 各自的取与不取,已经覆盖了 0~7 的所有情况。

1 2 4 全选是7 1不选是6 2不选是5 只选4 4不选是3 只选2 只选1 全部选 全部不选

确实挺神奇的,这样一来假如原本朴素版需要计算的次数是 2500次的话 ,就压到了 2500 -(1 + 2 + … + 1024)+ 10 ≈ 500次,

数字越大优化就越明显




13、链表:

单链表:

image-20240121150055097.png

数组模拟单向链表(静态链表):e[N]代表数组存储的value值,ne[N]代表该结点的下一位指针;

运用:

#include <iostream>
using namespace std;

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

// 初始化
void init()
{
    head = -1; // 初始化链表,-1表示空集
    idx = 0;
}

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

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

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

int main()
{
    ``````
    return 0;
}



双链表:

image-20240121155153192.png

数组模拟双向链表(静态链表):

#include <iostream>
using namespace std;

const int N = 100010;

int e[N], l[N], r[N], idx;

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

// 在下标是k的点的右边,插入x
void add(int k, int x)
{
    e[idx] = x;
    r[idx] = r[k];
    l[idx] = k;
    l[r[k]] = idx;
    r[k] = idx;
}

// 删除第k个点
void remove(int k)
{
    r[l[k]] = r[k];
    l[r[k]] = l[k];
}

int main()
{
    ``````
    return 0;
}




14、递归:

编写递归函数,将组成整数的所有数字逐个输出,每个数字后面加上一个减号”-“,例如对于整数123,该函数将输出1-2-3-。编写主函数测试该递归函数。

样例输入:

123

样例输出:

1-2-3-

void f(int n)
{
	if (n == 0)
		return;
	f(n / 10);       //举例输入123,则压栈顺序为123,12,3
	cout<<n%10<<'-'; //出栈顺序就为1-2-3-
}
int main()
{
	int n;
	cin >> n;
	f(n);
	return 0;
}

问题描述:

FJ在沙盘上写了这样一些字符串: A1 =“A” A2 =“ABA” A3 =“ABACABA” A4 =“ABACABADABACABA”

输入编号,输出对应字符串

string f(int n)
{
	if (n == 1)
		return "A";
	else
		return f(n - 1) + (char)('A' + n - 1) + f(n - 1);
}

int main()
{
	int n; cin >> n;
	cout << f(n) << endl;
	return 0;
}




15、进制转换

十进制转X进制:

void Binary(int n)
{
    short t = 0;
    if(n != 0)
    {
        t = n % X;
        n = n / X;
        Binary(n);
        cout<<("%d",t);
    }
}
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇