1.取消同步
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
2.素数判断模板--简单方法、埃氏筛、欧拉筛
2.1简单方法
bool is_prime(int x)
{
if(x==1) return false;
if(x==2) return true;
else
{
for(int i = 2;i*i <= x;i++)
if(x%i==0) return false;
return true;
}
}
2.2埃氏筛
利用质因子进行,筛掉2,3,5,7...的倍数
const int N = 1e8;
bool is_prime[N+1];
for(int i = 2;i <= sqrt(n);i++)
{
if(is_prime[i]==0)//代表是素数
{
for(int j = i*i;j <= n;j+=i)
{
is_prime[j] = 1;//不是素数
}
}
}
2.3欧拉筛(比埃氏筛高效)
确保每个合数只被最小质因数筛掉,每个合数不会重复被筛去
const int N = 1e7;
int prime[N+1];
bool visit[N+1];
memset(visit,0,sizeof visit);
memset(prime,0,sizeof prime);
int cnt = 0;
for(int i = 2;i <= n;i++)
{
if(!visit[i])
{
prime[cnt++] = i;
}
for(int j = 0;j < cnt;j++)
{
if(i * prime[j] > n) break;
visit[i * prime[j]] = 1;
if(i % prime[j]==0) break;
}
}
3.字符串排序
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
string s[100];
int main()
{
int n;
cin>>n;
for(int i = 0;i < n;i++)
cin>>s[i];
sort(s,s+n);
for(int i = 0;i < n;i++)
cout<<s[i];
return 0;
}
//假如有三个字符串abc,bef,bcd,排序后就是abc,bcd,bef
4.排序函数sqrt,sqrtl,sqrtf
sqrt()函数--适用于双精度数据,返回double类型
sqrtf()函数--适用于浮点型数据,返回float类型
sqrtl()函数--适用于长双进度类型,返回long double类型
5.优先队列--实现自动排序
从小到大:priority_queue<int,vector<int>,greater<int> >q;
从大到小:priority_queue<int>q;
优先队列每次讲优先级最高的元素先出队列
题目讲解:https://www.luogu.com.cn/problem/P2085
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
priority_queue< int,vector<int>,greater<int> >q;
int n,m;
long long sum(int a,int b,int c)//(f(x) = ax^2+bx+c)
{
for(int i = 1;i <= 100;i++)
{
long long sum;
sum = a * i * i + b * i + c;
q.push(sum);
}
}
int main()
{
cin>>n>>m;
while(n--){
int a,b,c;
cin>>a>>b>>c;
sum(a,b,c);
}
while(m--){
cout<<q.top()<<" ";
q.pop();
}
return 0;
}
6.字符串与数字的转换
字符串转化为数字:
int num = stoi(str);(转化为int)
int num = stol(str);(转化为long)
int num = stoll(str);(转化为longlong)
数字转化为字符串:
string str = to_string(num);
7.快速幂与矩阵快速幂
7.1快速幂
假设我们需要计算2^25次方,按照传统的方法需要计算25次,而对于快速幂算法只需要计算三次
25=1+8+16 | 00011001 |
1 | 00000001 |
8 | 00001000 |
16 | 00010000 |
long long fastpow(long long a,long long n,long long m)
{
if(n == 0) return 1;
if(n == 1) return a;
long long temp = fastpow(a,n/2,m);
if(n%2 == 1) return temp * temp * a % m;//奇数
else return temp * temp % m;//偶数
}
7.2矩阵快速幂
题目:https://www.luogu.com.cn/problem/P3390
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
const int mod = 1e9+7;
long long n,k;
struct matrix
{
long long m[N][N];//使用二维数组存储矩阵
};
matrix operator * (const matrix& a,const matrix& b)//重载乘法运算符以支持矩阵乘法
{
matrix c;
memset(c.m,0,sizeof(c.m));//初始化c矩阵
for(int i = 0;i < N;i++)
{
for(int j = 0;j < N;j++)
{
for(int k = 0;k < N;k++)
{
c.m[i][j] = (c.m[i][j] + a.m[i][k]*b.m[k][j])%mod;
}
}
}
return c;
}
//* 外层循环变量`i`遍历结果矩阵`c`的行。
//* 中层循环变量`j`遍历结果矩阵`c`的列。
//* 内层循环变量`k`是中间变量,用于计算`a`的第`i`行和`b`的第`j`列的点积,即矩阵乘法的核心步骤。
//* 在每次内层循环中,我们都将`a`的第`i`行第`k`列的元素与`b`的第`k`行第`j`列的元素相乘并将结果
//加到`c`的第`i`行第`j`列的元素上。注意,这里还使用了模数`mod`进行取模操作,以避免整数溢出。
int main()
{
cin>>n>>k;
matrix A;
for(int i = 0;i < n;i++)
{
for(int j = 0;j < n;j++)
{
cin>>A.m[i][j];
}
}
matrix ans;
memset(ans.m,0,sizeof(ans.m));
for(int i = 0;i < n;i++)
{
ans.m[i][i] = 1;
}
while(k)
{
if(k&1)//奇数
{
ans = ans * A;
}
A = A * A;
k>>=1;
}
for(int i = 0;i < n;i++)
{
for(int j = 0;j < n;j++)
{
cout<<ans.m[i][j]<<" ";
}
cout<<endl;
}
return 0;
}
8.阶乘之和1! + 2! + 3! + ... + n!
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1010;
int a[maxn],res[maxn];//a 被用来计算 i!,而 res 被用来存储所有阶乘的和。
int main()
{
int n;
cin>>n;
a[0] = 1;//阶乘从1开始,所以 a[0] 初始化为1(代表1!),
res[0] = 1;//res[0] 也初始化为1(代表1!的和)
for(int i = 2;i <= n;i++)
{
int carry = 0;//判断进位
for(int j = 0;j < maxn;j++)
{
a[j] = carry + a[j] * i;
carry = a[j]/10;
a[j]%=10;
}
//计算i的阶乘
for(int j = 0;j < maxn;j++)
{
res[j] += a[j];
res[j+1] += res[j]/10;
res[j]%=10;
}
//将结果加到res中
}
int len = maxn;
while(res[len-1]==0&&len>1)//去除前导0
{
len--;
}
for(int i = len-1;i >= 0;i--)//逆序输出
{
cout<<res[i];
}
return 0;
}
9.进制转换
printf("%05o\n",35); //按八进制格式输出,保留5位高位补零
printf("%03d\n",35); //按十进制格式输出,保留3位高位补零
printf("%05x\n",35); //按十六进制格式输出,保留5位高位补零
9.1任意2-36进制数转化为10进制数
int Atoi(string s,int radix) //s是给定的radix进制字符串
{
int ans=0;
for(int i=0;i<s.size();i++)
{
char t=s[i];
if(t>='0'&&t<='9') ans=ans*radix+t-'0';//这个数小于10
else ans=ans*radix+t-'A'+10;//这个是大于等于A
}
return ans;
}
9.2将10进制数转换为任意的n进制数,结果为char型
string decimalToBase(int decimal, int base) {
string digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
string result = "";
while (decimal > 0) {
result = digits[decimal % base] + result;
decimal /= base;
}
return result.empty() ? "0" : result;
}
9.3利用函数
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n;
cin>>n;
cout<<n<<"的八进制为:"<<oct<<n<<endl;
cout<<n<<"的十进制为:"<<dec<<n<<endl;
cout<<n<<"的十六进制为:"<<hex<<n<<endl;
cout<<n<<"的二进制为:"<<bitset<8>(n)<<endl;
return 0;
}
10.辗转相除法与最小公倍数
辗转相除法:最大公因数
int gcd(int a int b)
{
return b?gcd(b,a%b):a;
}
//或者直接利用函数 __gcd(a,b)
最小公倍数
int lcm(int a,int b)
{
return a/gcd(a,b)*b;
}
11.高精度加减乘除
11.1高精度加法
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
string s1,s2; //将所需要计算的数字以字符串的形式进行输入
int len1,len2,len; //标记字符串的长度
int a[10005],b[10005]; //将字符串的每一位转化为数字,即将字符串转换为数字
int main()
{
cin>>s1>>s2;
len1 = s1.size(),len2 = s2.size();
len = max(len1,len2);//选出更大的数的长度,再进行运算
for(int i = 0;i < len1;i++)
{
a[i] = s1[len1 - i- 1] - '0'; //转化为数字
}
for(int i = 0;i < len2;i++)
{
b[i] = s2[len2 - i - 1] - '0';
}
for(int i = 0;i < len;i++)
{
a[i] = a[i] + b[i]; //将a[i] + b[i]的值赋值给a[i];
a[i+1] = a[i+1] + a[i]/10; //a[i+1]的值为当前的值加上进位的数,进位的数等于a[i]/10;
a[i] %= 10; //取余,每个数不超过10,前面已经将进位的数加上去了,直接取余就是结果
}
if(a[len]!=0&&len>0) //a[len]!=0代表超出当前的位数,比如三位数加三位数可能为四位数,
//此时长度加1,最多也就加1,因为三位数加三位数不会为五位数
{
len++;
}
for(int i = len-1;i >= 0;i--)
{
cout<<a[i];
}
//逆序输出结果
return 0;
}
11.2高精度减法
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
string s1,s2;
int a[10005],b[10005],c[10005]; //用c数组来存储结果
int len1,len2,len;
int flag = 0; //用来标记是否需要负号
bool compare(string s1,string s2) //两个字符串进行大小比较
{
int len1 = s1.size(),len2 = s2.size();
if(len1!=len2) //两个字符串不相等
{
if(len1>len2) //s1>s2就return true
return true;
else
return false;
}
//两个字符串相等
for(int i = 0;i < len1;i++)
{
if(s1[i]!=s2[i])
{
if(s1[i] > s2[i]) //s1>s2就return true
return true;
else
return false;
}
}
return true; //两个字符串完全相等
}
int main()
{
cin>>s1>>s2;
if(!compare(s1,s2))//s2>s1
{
flag = 1; //负号
s1.swap(s2); //将两个字符串交换
}
len1 = s1.size(),len2 = s2.size();
len = max(len1,len2); //选出那个较长的字符串
for(int i = 0;i < len1;i++)
{
a[i] = s1[len1-i-1] - '0'; //将字符转换为数字
}
for(int i = 0;i < len2;i++)
{
b[i] = s2[len2-i-1] - '0';
}
for(int i = 0;i < len;i++)
{
if(a[i] < b[i]) //借位运算
{
a[i+1]--;
a[i] = a[i] + 10;
}
c[i] = a[i] - b[i];
}
while(c[len-1]==0&&len>1) //去除前导0
{
len--;
}
if(flag==1) //flag==1输出负号
{
cout<<"-";
}
for(int i = len-1;i >= 0;i--)
{
cout<<c[i]; //逆序输出结果
}
return 0;
}
11.3高精度乘法
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
string s1,s2;
int a[10005],b[10005],c[10005];
int len1,len2,len;
int main()
{
cin>>s1>>s2;
len1 = s1.size(),len2 = s2.size();
len = len1 + len2;//两个数相乘结果最长为len1+len2,如两位数与两位数相乘最大结果为四位数
for(int i = 0;i < len1;i++)
{
a[i] = s1[len1-1-i] - '0';
}
for(int i = 0;i < len2;i++)
{
b[i] = s2[len2-1-i] - '0';
}
for(int i = 0;i < len1;i++)
{
for(int j = 0;j < len2;j++)
{
c[i+j] += a[i] * b[j];
}
}
int x = 0;
for(int i = 0;i < len;i++)
{
c[i] += x;
x = c[i]/10;
c[i] %= 10;
}
while(c[len-1]==0&&len>1)
{
len--;
}
for(int i = len-1;i>=0;i--)
{
cout<<c[i];
}
return 0;
}
11.4高精度除法
高精度/低精度
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
string s1;
long long b;
long long a[10005],c[10005];
long long len;
long long d = 0;
int main()
{
cin>>s1;
cin>>b;
len = s1.size();
for(int i = 0;i < len;i++)
{
a[i] = s1[len-1-i] - '0';
}
for(int i = len-1;i >= 0;i--)
{
d = d*10 + a[i];
c[i] = d / b;
d %= b;
}
while(c[len]==0&&len>0)
{
len--;
}
for(int i = len;i >= 0;i--)
{
cout<<c[i];
}
return 0;
}
高精/高精
#include <stdio.h>
#include<string.h>
char arra[20000] = { 0 }, arrb[20000] = { 0 };//分别储存a,b的值
int ans[20000] = { 0 };//储存商
int judge(char* arr1, char* arr2, int len)//判断是否可以相减的函数
{
if (arr1[len] >'0') return 1; //如果arr1比arr2长, 则可以除
for (int i = len - 1; i >= 0; i--) {//从arr的最高位开始与arr2比较
if (arr1[i] > arr2[i]) return 1;//相同位时arr1中的数字更大,则可以相除
else if (arr1[i] < arr2[i]) return 0;//相同位时arr1数字更小则不能相除
}
return 1;//arr1和arr2完全一样,可以相除
}
void my_reverse(char* arr, int len)//翻转函数
{
for (int i = 0; i < len - 1; i++, len--)
{
char temp = arr[i];
arr[i] = arr[len - 1];
arr[len - 1] = temp;
}
}
void print_div(int len1, int len2, char* arr1, char* arr2, int* ans)
{
for(int i = len1-len2;i>=0;i--)//从最高位开始
{
while (judge(arr1 + i, arr2, len2))//判定是否可以相减
{
for (int j = 0; j < len2; j++)//高精度减法
{
if (arr1[i + j] < arr2[j])
{
arr1[i + j + 1] -= 1;
arr1[i + j] += 10;
}
arr1[i + j] -= (arr2[j] - '0');
}
ans[i]++;//ans[i]不可能>10
}
}
int len_ans = len1 - len2;//ans的长度
while (arr1[len1] == '0' && len1 > 0) len1--;//去掉前缀无用的零
while (ans[len_ans] == 0 && len_ans > 0) len_ans--;
for (int i = len_ans; i >= 0; i--)//打印商
{
printf("%d", ans[i]);
}
printf("\n");
//如果想要得到余数,则直接打印arr1即可,此时arr1存储的正是余数需要余数直接把下面的注释消掉即可
//if (len1 > 0||arr1[0]>='0')
// for (int i = len1 - 1; i >= 0; i--)
// printf("%c", arr1[i]);
}
int main()
{
scanf("%s %s", arra, arrb);
int lena = strlen(arra);//计算a和b的长度
int lenb = strlen(arrb);
my_reverse(arra, lena);
my_reverse(arrb, lenb);
print_div(lena, lenb, arra, arrb, ans);
return 0;
}
优化算法--傅里叶实现高精度乘法
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
const double PI = acos(-1.0);
struct Complex {
double x, y;
Complex(double _x = 0.0, double _y = 0.0) {
x = _x;
y = _y;
}
Complex operator-(const Complex &b) const {
return Complex(x - b.x, y - b.y);
}
Complex operator+(const Complex &b) const {
return Complex(x + b.x, y + b.y);
}
Complex operator*(const Complex &b) const {
return Complex(x * b.x - y * b.y, x * b.y + y * b.x);
}
};
/*
* 进行 FFT 和 IFFT 前的反置变换
* 位置 i 和 i 的二进制反转后的位置互换
*len 必须为 2 的幂
*/
void change(Complex y[], int len) {
int i, j, k;
for (int i = 1, j = len / 2; i < len - 1; i++) {
if (i < j) std::swap(y[i], y[j]);
// 交换互为小标反转的元素,i<j 保证交换一次
// i 做正常的 + 1,j 做反转类型的 + 1,始终保持 i 和 j 是反转的
k = len / 2;
while (j >= k) {
j = j - k;
k = k / 2;
}
if (j < k) j += k;
}
}
/*
* 做 FFT
*len 必须是 2^k 形式
*on == 1 时是 DFT,on == -1 时是 IDFT
*/
void fft(Complex y[], int len, int on) {
change(y, len);
for (int h = 2; h <= len; h <<= 1) {
Complex wn(cos(2 * PI / h), sin(on * 2 * PI / h));
for (int j = 0; j < len; j += h) {
Complex w(1, 0);
for (int k = j; k < j + h / 2; k++) {
Complex u = y[k];
Complex t = w * y[k + h / 2];
y[k] = u + t;
y[k + h / 2] = u - t;
w = w * wn;
}
}
}
if (on == -1) {
for (int i = 0; i < len; i++) {
y[i].x /= len;
}
}
}
const int MAXN = 200020;
Complex x1[MAXN], x2[MAXN];
char str1[MAXN / 2], str2[MAXN / 2];
int sum[MAXN];
int main() {
while (scanf("%s%s", str1, str2) == 2) {
int len1 = strlen(str1);
int len2 = strlen(str2);
int len = 1;
while (len < len1 * 2 || len < len2 * 2) len <<= 1;
for (int i = 0; i < len1; i++) x1[i] = Complex(str1[len1 - 1 - i] - '0', 0);
for (int i = len1; i < len; i++) x1[i] = Complex(0, 0);
for (int i = 0; i < len2; i++) x2[i] = Complex(str2[len2 - 1 - i] - '0', 0);
for (int i = len2; i < len; i++) x2[i] = Complex(0, 0);
fft(x1, len, 1);
fft(x2, len, 1);
for (int i = 0; i < len; i++) x1[i] = x1[i] * x2[i];
fft(x1, len, -1);
for (int i = 0; i < len; i++) sum[i] = int(x1[i].x + 0.5);
for (int i = 0; i < len; i++) {
sum[i + 1] += sum[i] / 10;
sum[i] %= 10;
}
len = len1 + len2 - 1;
while (sum[len] == 0 && len > 0) len--;
for (int i = len; i >= 0; i--) printf("%c", sum[i] + '0');
printf("\n");
}
return 0;
}
12.叉积判断线段是否相交
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
const double eps = 1e-8;
int sgn(double x)
{
if(fabs(x)<eps)
return 0;
else
return x<0?-1:1;
}
struct Point{
int x,y;
Point(){}//这是 Point 的默认构造函数,用于创建一个点,其坐标默认为0
Point (int x,int y):x(x),y(y){}// 这是一个带参数的构造函数,用于创建一个点并初始化其坐标。
Point operator + (Point B){return Point(x+B.x,y+B.y);}
//这是一个重载的加法运算符,它允许两个 Point 对象相加
//实际上,这里的加法是向量的加法,即计算两个点之间的位移向量
Point operator - (Point B){return Point(x-B.x,y-B.y);}
// 这是一个重载的减法运算符,它允许从当前点中减去另一个点,得到的是从 B 到当前点的位移向量。
};
typedef Point Vector;
double Cross(Vector A,Vector B)//叉积
{
return A.x*B.y - A.y*B.x;
}
bool Cross_segment(Point a,Point b,Point c,Point d)//相交返回1
{
double c1 = Cross(b-a,c-a),c2 = Cross(b-a,d-a);
double d1 = Cross(d-c,a-c),d2 = Cross(d-c,b-c);
return sgn(c1)*sgn(c2)<0 && sgn(d1)*sgn(d2)<0;
}
int main()
{
Point a,b,c,d;
cin>>a.x>>a.y>>b.x>>b.y>>c.x>>c.y>>d.x>>d.y;
if(Cross_segment(a,b,c,d)) cout<<"1";
else cout<<"0";
return 0;
}
13.并查集
void init_set()//初始化
{
for(int i = 1;i <= N;i++) s[i] = i;
}
int find_set(int x)//查找
{
return x==s[x]?x:find_set(s[x]);
}
void merge_set(int x,int y)//合并
{
x = find_set(x),y = find_set(y);
if(x!=y)
{
s[x] = s[y];
}
}
题目讲解:https://www.luogu.com.cn/problem/P3367
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
long long n,m;
long long f[10000+10];
int find_set(int x)
{
if(f[x]==x)return x;
return f[x]=find_set(f[x]);
}
int main()
{
cin>>n>>m;
for(int i = 1;i <= n;i++) f[i] = i;//初始化
for(int i = 1;i <= m;i++)
{
int a,b,c;
cin>>a>>b>>c;
if(a==1) f[find_set(b)] = find_set(c);
else
{
int rootx = find_set(b);
int rooty = find_set(c);
if(rootx==rooty) cout<<"Y"<<endl;
else cout<<"N"<<endl;
}
}
return 0;
}
14.递归
递归概念:在定义自身的同时又出现自身的直接或间接调用
注意:递归必须要有一个退出的条件!
14.1利用递归实现排列型枚举
题目:https://www.luogu.com.cn/problem/P1706
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
int n;
int s[15];
bool used[15];
void dfs(int u)
{
if(u>n)//结束条件,边界
{
for(int i = 1;i <= n;i++)
{
cout<<setw(5)<<s[i];//代表五个场宽
}
cout<<endl;
}
for(int i = 1;i <= n;i++)
{
if(!used[i])//数字i没有被用过
{
used[i] = true;//标记
s[u] = i;
dfs(u+1);
used[i] = false;//回溯--恢复原先的状态
s[u] = 0;
}
}
}
int main()
{
cin>>n;
dfs(1);
return 0;
}
14.2利用递归实现组合型枚举
题目:https://www.luogu.com.cn/problem/B3623
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
int n,k;
int used[15];
bool vis[15];
void dfs(int step){
if(step==k){
for(int i = 1;i <= k;i++){
cout<<used[i]<<" ";
}
cout<<endl;
return; //到头了回溯--继续dfs搜索余下的情况
}
for(int i = 1;i <= n;i++){
if(vis[i]==false){ //等于
vis[i] = true; //赋值
used[step+1] = i;
dfs(step+1);
vis[i] = false; //步骤回溯
}
}
}
int main(){
memset(vis,false,sizeof(vis)); //memset(数组名,是/否,数组大小sizeof())
cin>>n>>k;;
dfs(0);
return 0;
}
14.3利用递归实现指数型枚举
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
int n;
int s[15];//用来记录每个点的状态
// 0 表示待考虑,1 表示选择该数, 2 表示不选
void dfs(int x)
{
if(x==n)
{
for(int i = 0;i < n;i++)
{
if(s[i]==1)//表示选择该数
cout<<i+1<<" ";//输出
}
cout<<endl;
return;
}
s[x]=2;//第一个分支,不选
dfs(x+1);
s[x]=0;//回溯
s[x]=1;//第二个分支,选
dfs(x+1);
s[x]=0; //回溯
}
int main()
{
cin>>n;
dfs(0);
return 0;
}
15.比较两个浮点数是否相等
bool compareDouble(double a,double b)
{
double eps = 1e-6 //注意精度,默认是1e-6,有时候精度要求不高可以降低精度要求
if(fabs(a-b)<eps) return true;
return false;
}
16.全排列next_permutation与prev_permutation
int n,a[1000];
cin >> n;
for(int i = 0 ; i < n ; i++)
cin >> a[i];
do{
for(int i = 0 ; i < n ; i++)
cout << a[i] << " ";
cout << endl;
}while(next_permutation(a, a + n));
//
int n,a[1000];
cin >> n;
for(int i = 0 ; i < n ; i++)
cin >> a[i];
sort(a,a + n,greater<int>());//进行sort排序,greater<int>()即将待排数列变为降序
do{
for(int i = 0 ; i < n ; i++)
cout << a[i] << " ";
cout << endl;
}while(prev_permutation(a, a + n));
17.lower_bound()和upper_bound()
#include<bits/stdc++.h>
using namespace std;
const int maxn=100000+10;
const int INF=2*int(1e9)+10;
#define LL long long
int cmd(int a,int b){
return a>b;
}
int main(){
int num[6]={1,2,4,7,15,34};
sort(num,num+6); //按从小到大排序
int pos1=lower_bound(num,num+6,7)-num; //返回数组中第一个大于或等于被查数的值
int pos2=upper_bound(num,num+6,7)-num; //返回数组中第一个大于被查数的值
cout<<pos1<<" "<<num[pos1]<<endl;
cout<<pos2<<" "<<num[pos2]<<endl;
sort(num,num+6,cmd); //按从大到小排序
int pos3=lower_bound(num,num+6,7,greater<int>())-num; //返回数组中第一个小于或等于被查数的值
int pos4=upper_bound(num,num+6,7,greater<int>())-num; //返回数组中第一个小于被查数的值
cout<<pos3<<" "<<num[pos3]<<endl;
cout<<pos4<<" "<<num[pos4]<<endl;
return 0;
}
18.计算(a+b)^ n 的系数的方法
19.动态规划
能用动态规划解决的问题,需要满足三个条件:最优子结构,无后效性和子问题重叠
19.1题型一:最长公共子序列
给定一个长度为 n 的序列 A 和一个 长度为 m 的序列 B(n,m <=5000),求出一个最长的序列,使得该序列既是 A 的子序列,也是 B 的子序列。
//这个做法不适合数据量太大的情况
int a[MAXN], b[MAXM], f[MAXN][MAXM];
int dp() {
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
if (a[i] == b[j])
f[i][j] = f[i - 1][j - 1] + 1;
else
f[i][j] = std::max(f[i - 1][j], f[i][j - 1]);
return f[n][m];
}
19.2题型二:最长上升子序列/最长不下降子序列
给定一个长度为 n 的序列 A,求出一个最长的 A 的子序列,满足该子序列的后一个元素不小于前一个元素
第一种情况:(n <= 5000)
int a[5010];//代表原先的序列
int dp[5010];//dp[i]就是找以a[i]为结尾的,在a[i]之前的最长上升子序列+1
int work()
{
dp[1] = 1;
int ans = 1;
for(int i = 2;i <= n;i++)
{
dp[i] = 1;//初始时单个字符的最长子序列就是1
for(int j = 1;j < i;j++)
{
if(a[j] <= a[i])
{
dp[i] = max(dp[i],dp[j]+1);//当i为2时 ,j就是从1-2,a[j(=1)]=2 < a[i(=2)]=7
ans = max(ans,dp[i]);
}
}
}
return ans;
}
第二种情况:范围扩大到n<=10^5
现在我们已知最长的不下降子序列长度为 1,那么我们让 i 从 2 到 n 循环,依次求出前 i 个元素的最长不下降子序列的长度,循环的时候我们只需要维护好 d 这个数组还有 len 就可以了,考虑进来一个元素 a_i:元素大于等于 d_{len},直接将该元素插入到 d 序列的末尾,元素小于 d_{len},找到 第一个 大于它的元素,用 a_i 替换它。
int a[N];
int dp[N];
int len = 0;
int main()
{
cin>>n;
for(int i = 1;i <= n;i++)
{
cin>>a[i];
}
int len = 0;
for(int i = 1;i <= n;i++)
{
int pos = upper_bound(dp+1,dp+1+len,a[i])-dp;
//upper_bound(dp+1, dp+len+1, a[i]): 这调用upper_bound函数,
//它返回指向dp数组中第一个大于a[i]的元素的迭代器。
//如果dp中的所有元素都小于或等于a[i],则返回的迭代器将指向dp+len+1。
//upper_bound(...) - dp: 由于upper_bound返回的是一个迭代器,
//我们可以通过减去dp的基地址来得到该元素在dp中的位置(基于0的索引)。
dp[pos] = a[i];
len = max(len,pos);
}
cout<<len;
return 0;
}
19.3题型三:记忆化搜索
记忆化搜索是一种通过记录已经遍历过的状态的信息,从而避免对同一状态重复遍历的搜索实现方式。
题目:https://www.luogu.com.cn/problem/P1048
方法一:
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
int n,t;
int tcost[105],mget[105];
int men[105][1005];
#define INF 0x7fff
int dfs(int pos,int tleft)
{
if(men[pos][tleft] != -1)//不等于-1,说明之前访问过这个状态,直接返回即可
{
return men[pos][tleft];
}
if(pos == n+1)//结束条件
{
return men[pos][tleft] = 0;
}
int dfs1,dfs2 = -INF;
dfs1 = dfs(pos+1,tleft);
if(tleft >= tcost[pos])
{
dfs2 = dfs(pos+1,tleft - tcost[pos]) + mget[pos];//状态转移方程
}
return men[pos][tleft] = max(dfs1,dfs2);//将当前的状态值存下来
}
int main()
{
memset(men,-1,sizeof men);
cin>>t>>n;
for(int i = 1;i <= n;i++)
{
cin>>tcost[i]>>mget[i];
}
cout<<dfs(1,t);
return 0;
}
方法二:
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
int dp[110][110];
int t,m;
int Time[110],Val[110];
int main()
{
cin>>t>>m;
for(int i = 1;i <= m;i++)
{
cin>>Time[i]>>Val[i];
}
for(int i = 1;i <= m;i++)//时间
{
for(int j = t;j >= 0;j--)//数目
{
if(j>=Time[i])
{
dp[i][j] = max(dp[i-1][j],dp[i-1][j-Time[i]]+Val[i]);//选与不选
}
else
{
dp[i][j] = dp[i-1][j];
}
}
}
cout<<dp[m][t];
return 0;
}
19.4题型四:01背包(逆向)
题目:有 n 个物品和一个容量为 W 的背包,每个物品有重量 w_{i} 和价值 v_{i} 两种属性,要求选若干物品放入背包使背包中物品的总价值最大且背包中物品的总重量不超过背包的容量。
#include <iostream>
using namespace std;
const int maxn = 13010;
int n, W, w[maxn], v[maxn], f[maxn];
int main() {
cin >> n >> W;
for (int i = 1; i <= n; i++) cin >> w[i] >> v[i]; // 读入数据
for (int i = 1; i <= n; i++)
for (int l = W; l >= w[i]; l--)
if (f[l - w[i]] + v[i] > f[l]) f[l] = f[l - w[i]] + v[i]; // 状态方程
//或者f[j] =max(f[j], f[j - w[i]] + v[i]);
cout << f[W];
return 0;
}
附题讲解:https://www.luogu.com.cn/problem/P1216
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
int r;
int a[1010][1010];
int dp[1010][1010];
int solve(int R)//自底向上
{
for(int i = R;i>=0;i--)
{
for(int j = 1;j <= i;j++)//因为是三角形
{
dp[i][j] = max(dp[i][j] + dp[i+1][j],dp[i][j] + dp[i+1][j+1]);
}
}
return dp[1][1];
}
19.5题型五:完全背包(正向)
题目:https://www.luogu.com.cn/problem/P1616
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
long long t,m;
const int N = 1e4+10;
const int M = 1e7+10;
long long Time[N],Val[N];
long long dp[M];
int main()
{
cin>>t>>m;
for(int i = 1;i <= m;i++)
{
cin>>Time[i]>>Val[i];
}
for(int i = 1;i <= m;i++)
{
for(int j = Time[i];j <= t;j++)//保证有足够的时间采摘草药
{
dp[j] = max(dp[j],dp[j - Time[i]]+Val[i]);
}
}
cout<<dp[t];
return 0;
}
19.6题型六:多重背包
多重背包也是 0-1 背包的一个变式。与 0-1 背包的区别在于每种物品有 k_i 个
把「每种物品选 k_i 次」等价转换为「有 k_i 个相同的物品,每个物品选一次」。这样就转换成了一个 0-1 背包模型
题目:https://www.luogu.com.cn/problem/P1776
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
int n,m,ans,cnt=1;
int dp[1000005];
int w[1000005],v[1000005];//记得将数组开大
int main()
{
cin>>n>>m;
for(int i=1;i<=n;++i)
{
int a,b,c;//每种宝物的价值、重量、每件宝物有几件
cin>>a>>b>>c;
for(int j=1;j<=c;j<<=1)
{
v[++cnt]=j*a,w[cnt]=j*b;//++cnt,v[cnt] = j*a;
/*
v[++cnt]:
++cnt; // 先增加 cnt
int value = v[cnt]; // 然后访问 v[cnt]
v[cnt++]:
int value = v[cnt]; // 先访问 v[cnt]
cnt++; // 然后增加 cnt
*/
/*v[++cnt]:
首先,cnt 的值会增加 1。
然后,返回数组 v 中索引为 cnt(现在已经是新值)的元素的值。换句话说,你先更新 cnt,然后再使用更新后的 cnt 来访问数组元素。
v[cnt++]:
首先,返回数组 v 中索引为当前 cnt 的元素的值。
然后,cnt 的值会增加 1。换句话说,你先使用当前的 cnt 来访问数组元素,然后再更新 cnt。*/
c-=j;
}
if(c) //代表分剩下的数,例如13-1-2-4=6;
{
v[++cnt]=a*c,w[cnt]=b*c;
}
//二进制优化,拆分
}
for(int i=1;i<=cnt;i++)
{
for(int j=m;j>=w[i];j--)
{
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
}
//简单的多重背包
cout<<dp[m];
return 0;
}
19.7题型七:混合背包
混合背包就是将前面三种的背包问题混合起来,有的只能取一次,有的能取无限次,有的只能取 k次
题目:https://www.luogu.com.cn/problem/P1833
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
int h1,m1,h2,m2;
int Time;
int n, w[10020] , c[100010] , dp[100101] ,s[10500];//代表耗费时间、美学值、看树次数
int main()
{
scanf("%d:%d %d:%d",&h1,&m1,&h2,&m2);
Time = (h2-h1)*60 + (m2-m1);
//cout<<Time<<endl;
cin>>n;
for(int i = 1;i <= n;i++)
{
cin>>w[i]>>c[i]>>s[i];
}
for(int i = 1;i <= n;i++)
{
if(s[i]==0)//完全背包
{
for(int j = w[i];j <= Time;j++)
{
dp[j] = max(dp[j],dp[j-w[i]]+c[i]);
}
}
else//01背包与多重背包
{
for(int j = 1;j <= s[i];j++)
{
for(int k = Time;k >= w[i];k--)
{
dp[k] = max(dp[k],dp[k-w[i]]+c[i]);
}
}
}
}
cout<<dp[Time];
return 0;
}
19.8题型八:二维费用背包
选一个物品会消耗两种价值,只需在状态中增加一维存放第二种价值即可。
题目:https://www.luogu.com.cn/problem/P1855
#include <bits/stdc++.h>
using namespace std;
int n,M,T,dp[1010][1010];
int m[1010],t[1010];
int main()
{
cin>>n>>M>>T;
for(int i=1;i<=n;i++)
{
cin>>m[i]>>t[i];
for(int j=M;j>=m[i];j--)
{
for(int k=T;k>=t[i];k--)
{
dp[j][k]=max(dp[j][k],dp[j-m[i]][k-t[i]]+1);
}
}
}
cout<<dp[M][T];
}
19.9题型九:分组背包
从在所有物品中选择一件变成了从当前组中选择一件,于是就对每一组进行一次 0-1 背包就可以了。
题目:https://www.luogu.com.cn/problem/P1757
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
int n,m;
int dp[1010];
int order[1010],w[110][1010],val[110][1010];
int maxx = 1;
int main()
{
cin>>m>>n;
for(int i = 1;i <= n;i++)
{
int a,b,c;
cin>>a>>b>>c;
maxx = max(maxx,c);//代表有几组的意思
order[c]++;//第c组的第几个物品
w[c][order[c]] = a;
val[c][order[c]] = b;
}
for(int i = 1;i <= maxx;i++)//第i组
{
for(int j = m;j >= 0;j--)//背包容量
{
for(int k = 1;k <= order[i];k++)//物品序号
{
if(j >= w[i][k])
{
dp[j] = max(dp[j],dp[j-w[i][k]]+val[i][k]);
}
}
}
}
cout<<dp[m];
return 0;
}
20.二叉树
20.1先序遍历--按照 根,左,右 的顺序遍历二叉树
20.2中序遍历--按照 左,根,右 的顺序遍历二叉树
20.3后序遍历--按照 左,右,根 的顺序遍历二叉树
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
long long n;
int root = 1;//根节点为1
struct node{
long long lson,rson;
long long value;
}tree[100010];//创建二叉树
void preorder(int father)//前序遍历 -- 根左右
{
if(father!=0)
{
cout<<tree[father].value<<" ";
preorder(tree[father].lson);
preorder(tree[father].rson);
}
}
void inorder(int father)//中序遍历 -- 左根右
{
if(father!=0)
{
inorder(tree[father].lson);
cout<<tree[father].value<<" ";
inorder(tree[father].rson);
}
}
void postorder(int father)//后序遍历 -- 左右根
{
if(father!=0)
{
postorder(tree[father].lson);
postorder(tree[father].rson);
cout<<tree[father].value<<" ";
}
}
int main()
{
cin>>n;
for(int i = 1;i <= n;i++)
{
tree[i].value = i;
cin>>tree[i].lson>>tree[i].rson;
}
preorder(1);
cout<<endl;
inorder(1);
cout<<endl;
postorder(1);
return 0;
}
20.4二叉树的深度
二叉树的深度:
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
long long n;
int depth;
const int N = 1e6+5;
struct Node
{
int value,lson,rson;
}tree[N];//定义二叉树
void dfs(int a,int deep)
{
if(a==0)
return;
depth = max(deep,depth);
dfs(tree[a].lson,deep+1);
dfs(tree[a].rson,deep+1);
}
int main()
{
cin>>n;
for(int i = 1;i <= n;i++)
{
cin>>tree[i].lson>>tree[i].rson;
}
dfs(1,1);
cout<<depth;
return 0;
}
21.前缀和与差分
21.1一维前缀和--前缀和可以理解为数列的前n项的和
有n个的正整数放到数组a里,现在要求一个新的数组b新数组的第i个数b[i]是原数组a第0到第i个数的和(一维前缀和)
#include <iostream>
using namespace std;
int N, A[10000], B[10000];
int main() {
cin >> N;
for (int i = 0; i < N; i++) {
cin >> A[i];
}
// 前缀和数组的第一项和原数组的第一项是相等的。
B[0] = A[0];
for (int i = 1; i < N; i++) {
// 前缀和数组的第 i 项 = 原数组的 0 到 i-1 项的和 + 原数组的第 i 项。
B[i] = B[i - 1] + A[i];
}
for (int i = 0; i < N; i++) {
cout << B[i] << " ";
}
return 0;
}
21.2二维前缀和--s[i][j]
表示二维数组中,左上角(1, 1)
到右下角(i, j)
所包围的矩阵元素的和
前缀和公式:s[i][j] = s[i - 1][j] + s[i][j - 1 ] + a[i] [j] - s[i - 1][j - 1]
题目:https://www.luogu.com.cn/problem/P1719
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
int a[125][125],b[125][125];
int n;
int maxx = -0x7fffffff;
int sum(int x1,int y1,int x2,int y2)
{
return b[x2][y2] + b[x1 - 1][y1 - 1] - b[x2][y1 - 1] - b[x1 - 1][y2];
}
int main()
{
cin>>n;
for(int i = 1;i <= n;i++)
{
for(int j = 1;j <= n;j++)
{
cin>>a[i][j];
b[i][j] = b[i-1][j] + b[i][j-1] -b[i-1][j-1] + a[i][j];
}
}
for(int x1 = 1;x1 <= n;x1++)
{
for(int y1 = 1;y1 <= n;y1++)
{
for(int x2 = x1;x2 <= n;x2++)
{
for(int y2 = y1;y2 <= n;y2++)
{
maxx = max(maxx,sum(x1,y1,x2,y2));
}
}
}
}
cout<<maxx;
return 0;
}
21.3一维差分--可以看成前缀和的逆运算
差分有什么用?对原数组nums的区间[a, b]内的每个元素增加k,只需要修改差分数组diff的diff[a] += k和diff[b+1] -= k,原数组nums中区间[a, b]内的元素就增加了k,而不需要遍历整个区间进行逐个修改。
题目:https://www.luogu.com.cn/problem/P2367
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
const long long N = 5000005;
long long a[N],b[N];
long long n,p;
void insert(long long l,long long r,long long c)
{
b[l]+=c;
b[r+1]-=c;
}
int main()
{
cin>>n>>p;
int minx = 9999;
for(long long i = 1;i <= n;i++)
{
cin>>a[i];
}
for(long long i = 1;i <= n;i++)
{
insert(i,i,a[i]);
}
while(p--)
{
long long l, r, c;
cin>>l>>r>>c;
insert(l,r,c);
}
for(long long i = 1;i <= n;i++)
{
a[i] = a[i-1] + b[i];
}
for(int i = 1;i <= n;i++)
{
if(a[i]<minx)
minx = a[i];
}
cout<<minx;
return 0;
}
21.4 二维差分--二维前缀和逆运算
对于一个二维数据矩阵X,其差分矩阵Y的第(i, j)个元素可以通过以下公式计算得到: Y[i, j] = X[i+1, j] - X[i, j] + X[i, j+1] - X[i, j]
题目:https://www.luogu.com.cn/problem/P3397
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
int n,m;
int a[1010][1010],D[1010][1010];
void work(int x1,int y1,int x2,int y2)
{
D[x1][y1] += 1;
D[x1][y2+1] -= 1;
D[x2+1][y1] -= 1;
D[x2+1][y2+1] += 1;
}
int main()
{
cin>>n>>m;
for(int i = 1;i <= m;i++)
{
int x1,y1,x2,y2;
cin>>x1>>y1>>x2>>y2;
work(x1,y1,x2,y2);
}
for(int i = 1;i <= n;i++)
{
for(int j = 1;j <= n;j++)
{
a[i][j] = D[i][j] + a[i-1][j] + a[i][j-1] - a[i-1][j-1];
cout<<a[i][j]<<" ";
}
cout<<endl;
}
return 0;
}
22.二分法
二分查找是一种在有序数组中查找某一特定元素的搜索算法。
搜索过程:
从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组为空,则代表找不到。
22.1二分查找
题目:https://www.luogu.com.cn/problem/P2249
#include <bits/stdc++.h>
using namespace std;
long long n,m;
const int N = 1e6 + 10;
long long a[N];
long long research(long long x)
{
long long left = 1,right = n;
while(left < right)
{
//long long mid = left + (right - left)/2;
long long mid = (left + right)/2;
if(a[mid] >= x)
{
right = mid;
}
else
left = mid + 1;
}
if(a[left] == x)
return left;
else
return -1;
}
int main()
{
cin>>n>>m;
for(int i = 1;i <= n;i++)
{
cin>>a[i];
}
while(m--)
{
long long x;
cin>>x;
cout<<research(x)<<" ";
}
return 0;
}
22.2二分答案
题目:https://www.luogu.com.cn/problem/P1824
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
const int N = 100000+10;
int n,c,x[N];
bool check(int dis)
{
int cnt = 1,place = 0;
for(int i = 1;i < n;i++)
{
if(x[i] - x[place]>=dis)//如果距离dis的位置有牛棚
{
cnt++;//再放一头牛
place = i;
}
}
if(cnt>=c)
return true;
else
return false;
}
int main()
{
cin>>n>>c;
for(int i = 0;i < n;i++)
{
cin>>x[i];
}
sort(x,x+n);
int left = 0;
int right = x[n-1] - x[0];//范围
int ans = 0;
while(left <right)
{
int mid = left + (right - left)/2;
if(check(mid)) //扩大距离
{
ans = mid;
left = mid + 1;
}
else
right = mid; //缩小距离
}
cout<<ans;
}
23.基本数据结构算法
23.1map 用法
map<key,value>,每个关键字key只能出现一次,value可以出现多次
mp.erase(key); // 删除值为 key 的元素
mp.erase(iter); // 删除迭代器 iter 指向的元素,例如
mp.erase(mp.begin());
mp.erase(iter1, iter2); // 删除区间 [iter1, iter2) 的所有元素,例
如 mp.erase(mp.begin(), mp.end());
mp.clear(); // 清空集合
// 求大小
int siz = mp.size(); // 求集合大小
bool flag = mp.empty(); // 集合判空
// 查询
if(mp.find(key) != mp.end()) // find 函数返回一个指向被查找到元素的迭代器
cout << mp[key] << endl;
if(mp.count(key)) // count 返回某个值元素的个数
cout << mp[key] << endl;
auto iter = mp.lower_bound(key); // 求 key 的下界,返回指向大于等于某值的第一个元素的迭代器
auto iter = mp.upper_bound(key); // 求 key 的上界,返回大于某个值元素的迭代器
// 遍历
map<string, int>::iterator iter; // 正向遍历
for(iter=mp.begin(); iter!=mp.end(); iter++)
{
cout << iter->first << " " << iter->second << endl;
// 或者
cout << (*iter).first << " " << (*iter).second << endl;
}
map<int>::reverse_iterator riter; // 反向遍历
for(riter=mp.rbegin(); riter!=mp.rend(); riter++)
{
// 遍历的同时修改
iter->second += 10;
cout << iter->first << " " << iter->second << endl;
}
// 求最值
map<string, int>::iterator it = mp.begin(); // 最小值
cout << *it << endl;
map<string, int>::iterator it = mp.end(); // 最大值
cout << *(--it) << endl;
23.2set--有序的容器
// 插入
s.insert(key); // 插入
// 删除
s.erase(key); // 删除值为 key 的元素
s.erase(iter); // 删除迭代器 iter 指向的元素,例如
s.erase(s.begin());
s.erase(iter1, iter2); // 删除区间 [iter1, iter2) 的所有元素,例
如 s.erase(s.begin(), s.end());
s.clear(); // 清空集合
// 求大小
int siz = s.size(); // 求集合大小
bool flag = s.empty(); // 集合判空
// 查询
if(s.find(key) != s.end()) // find 函数返回一个指向被查找到元素的迭代器
cout << "exist" << endl;
if(s.count(key) == 1) // count 返回某个值元素的个数
cout << "exist" << endl;
set<int>::iterator iter = s.lower_bound(key); // 求 key 的下界,返回指向大于等于某值的第一个元素的迭代器
set<int>::iterator iter = s.upper_bound(key); // 求 key 的上界,返回大于某个值元素的迭代器
// auto 类型推断关键字 在NOI 系列比赛中也可以使用了
auto iter = s.lower_bound(key); // 求 key 的下界,返回指向大于等于某值的第一个元素的迭代器
auto iter = s.upper_bound(key); // 求 key 的上界,返回大于某个值元素的迭代器
// 遍历
set<int>::iterator iter; // 正向遍历
for(iter=s.begin(); iter!=s.end(); iter++)
{
cout<<*iter<<endl;
}
set<int>::reverse_iterator riter; // 反向遍历,不重要
for(riter=s.rbegin(); riter!=s.rend(); riter++)
{
cout<<*riter<<endl;
}
// 求最值
set<int>::iterator it = s.begin(); // 最小值
cout << *it << endl;
set<int>::iterator it = s.end(); // 最大值
cout << *(--it) << endl;
23.3栈--先进后出
栈的特点是“先进后出”;栈需要空间存储,如果站的深度太大,或者存进栈的数组太大,哪呢就会超出系统分配给栈的空间,导致栈溢出。
n个元素进栈,有C(2n,n)/(n+1) (C(2n,n)表示2n里取n)个出栈顺序(称为卡特兰数)
long long res[67][67]={0};
long long C(long long n,long long m){
if(m==0 || m==n) return 1;
if(res[n][m] != 0)return res[n][m];
return res[n][m] = C(n-1,m)+ C(n-1,m-1);
}
int fun(int n)
{
long long sum = C(2*n,n);
return sum/(n+1);
}
STL栈的主要操作
(1)stack<Type>s 定义栈的数据类型
(2)s.push(item) 将元素item放到栈顶
(3)s.top() 返回栈顶元素,且不会删除
(4)s.pop() 删除栈顶的元素,且不会返回,出栈时先使用top()获得栈顶元素,再用pop()删除栈顶元素
(5)s.size() 返回栈的元素个数
(6)s.empty() 判断栈是否为空,为空就返回true,否则返回false
手写栈
struct mystack{
char a[10010];
int t = 0;
void push(char x){ //存放栈元素
a[++t] = x;
}
char top(){
return a[t]; //返回栈顶元素
}
char pop(){ //弹出栈顶
t--;
}
int empty(){ //判断栈是否为空,为空返回1
return t==0?1:0;
}
}st;
单调栈
单调栈是栈的一种适用形式,单调栈内的元素是单调递增或者单调递减的,有单调递增栈与单调递减栈,单调递减栈从栈顶到栈底是从小到大的顺序。
题目:https://www.luogu.com.cn/problem/P5788
//输入输出用scanf 与 printf,用cin cout会TLE
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
const int N = 3000000+10;
int n;
int a[N];
int rel[N];
stack<int>st;
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n;i++)
{
scanf("%d",&a[i]);
}
for(int i = n;i >= 1;i--)
{
while(!st.empty()&&a[st.top()]<=a[i]) //栈顶元素没有i高,就弹出它,知道栈顶奶牛更高
{
st.pop();
}
if(st.empty()) //栈空,没有比他高的
rel[i] = 0;
else
rel[i] = st.top(); //输出栈顶
st.push(i);
}
for(int i = 1;i <= n;i++)
{
printf("%d ",rel[i]);
}
return 0;
}
23.4队列--先进后出
队列基础知识:先进先出,只能在队尾添加数据,从队头移出数据。
STL queue的基本操作
(1)queue<Type>q;---定义队列,Type可以是int,float,char等。
(2)q.push(i);------将元素i放进队列
(3)q.pop();--------将队首元素删除
(4)q.back();-------返回队尾元素
(5)q.front();------返回队首元素
(6)q.size();-------返回元素个数
(7)q.empty();-------检查队列是否为空
题目:https://www.luogu.com.cn/problem/B3616
#include <bits/stdc++.h>
using namespace std;
queue <int> q;
const int N = 10000+10;
int a[N];
int main()
{
int n;
cin>>n;
while(n--)
{
int x,y;
cin>>x;
if(x==1) //直接将y加入队列
{
cin>>y;
q.push(y);
}
else if(x==2) //判断队列是否为空,为空输出“ERR_CANNOT_POP”,不为空删除队首元素
{
if(q.empty())
{
cout<<"ERR_CANNOT_POP"<<endl;
}
else
{
q.pop();
}
}
else if(x==3) //判断队列是否为空,为空输出“ERR_CANNOT_POP”,不为空输出队首元素
{
if(q.empty())
{
cout<<"ERR_CANNOT_QUERY"<<endl;
}
else
cout<<q.front()<<endl;
}
else
cout<<q.size()<<endl; //输出队列大小
}
return 0;
}
手写循环队列
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
#define N 1003
int hash[N] = {0};
struct Myqueue{ //分配静态空间
int data[N];
int head,rear;
bool init() //初始化队列
{
head = rear = 0;
return true;
}
int size() //返回队列大小
{
return (rear - head + N) % N;
}
bool empty() //判断队列是否为空
{
if(size()==0)
return true;
else
return false;
}
bool push(int e) //添加元素e
{
if((rear + 1) % N == head) //队列满,无法添加元素
return false;
data[rear] = e; //将元素加进队列
rear = (rear + 1) % N; //因为是循环队列
return true;
}
bool pop(int &e) //删除元素
{
if(head==rear) //队列为空 ,不能删除
return false;
e = data[head]; //将元素e赋值为队首元素,删除
head = (head + 1) % N;
return true;
}
int front() //取队首元素
{
return data[head];
}
}Q;
int main()
{
Q.init(); //进行初始化
for(int i = 0;i < 10;i++)
{
Q.push(i); //将元素入队
}
//取队首元素
cout<<Q.front()<<endl;
//判断队列是否为空,为空输出1,否则输出0
if(Q.empty())
{
cout<<"1"<<endl;
}
else
{
cout<<"0"<<endl;
}
//队列元素个数
cout<<Q.size()<<endl;
while(!Q.empty())
{
int tep = Q.front();
cout<<tep<<" ";
Q.pop(tep);
}
cout<<endl;
//再次判断队列是否为空
if(Q.empty())
{
cout<<"1"<<endl;
}
else
{
cout<<"0"<<endl;
}
return 0;
}
双端队列
同时具有队列和栈性质的数据结构,能够在两端进行插入和删除。
主要操作:
双端队列为deque,有以下基本用法
(1)dp[i] -- 返回元素为i的个数
(2)dp.front() -- 返回队头
(3)dp.back() -- 返回队尾
(4)dp.pop_back() -- 删除队尾,无返回值
(5)dp.pop_front() -- 删除队头,无返回值
(6)dp.push_back(e) -- 队尾添加一个元素
(7)qp.push_front(e) -- 在队头添加一个元素
单调队列--滑动窗口问题
题目:https://www.luogu.com.cn/problem/P1886
//解析:x进入队尾时,和原先的队尾y进行比较,如果x<=y,就从队尾弹出y;
//知道找到比x大的元素,x进入队尾 -- 保证队头元素最小
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6+5;
int n,k;
int a[N];
deque <int> q;
//原理:将查询的区间依次推进单调队列然后访问第一个数,就是最大值或者最小值
//输出的第一个数是最小值时,队列时递增的,后面的数大于前面的数
int main()
{
cin>>n>>k;
for(int i = 1;i <= n;i++)
{
cin>>a[i];
}
for(int i = 1;i <= n;i++) //模板
{
while(!q.empty()&&a[i] < a[q.back()])
//待变队列不为空 输出最小值
//队列最后一个位置的元素的值大于i位置的值时
{
q.pop_back(); //去尾 -- 删除队尾元素
}
q.push_back(i); //将区间推进单调队列
if(i>=k) //区间大于k时 每个窗口输出一次
{
while(!q.empty()&&q.front()<=i-k)
{
q.pop_front(); //删头
}
cout<<a[q.front()]<<" ";
}
}
cout<<endl;
while(!q.empty())
q.pop_front(); //清空
for(int i = 1;i <= n;i++)
{
while(!q.empty()&&a[i]>a[q.back()])
{
q.pop_back(); //去尾
}
q.push_back(i) ;
if(i >= k)
{
while(!q.empty()&&q.front()<=i-k)
{
q.pop_front(); //删头
}
cout<<a[q.front()]<<" ";
}
}
cout<<endl;
return 0;
}
优先队列在第五点
24.最长连续不重复子区间
遍历数组 𝑎 中的每一个元素 𝑎[𝑖] , 对于每一个𝑖,找到j使得双指针 [𝑗,𝑖] 维护的是以 𝑎[𝑖] 结尾的最长连续不重复子序列,长度为 𝑖−𝑗+1 , 将这一长度与 𝑟𝑒𝑠 的较大者更新给𝑟𝑒𝑠
#include<iostream>
using namespace std;
const int N = 100010;
int a[N], st[N]; // st:标记函数,可以使用哈希表代替
int n;
int main()
{
cin >> n;
for(int i = 0; i < n; ++i){
cin >> a[i];
}
int res = 1;
for(int i = 0, j = 0; i <n; ++i){
st[a[i]] ++;
while(j <= i && st[a[i]] > 1)
{ // 将慢指针往前移动
st[a[j]] --;
j ++;
}
res = max(res, i - j + 1);
}
cout << res << endl;
return 0;
}
25.线段树
线段树是算法竞赛中常用的用来维护 区间信息 的数据结构。 线段树可以在 的时间复杂度内实现单点修改、区间修改、区间查询(区间求和,求区间最大值,求区间最小值)等操作。
题目链接:https://www.luogu.com.cn/problem/P3372
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
#define lowbit(x) ((x)&-(x))
long long tree1[N],tree2[N];
long long n,m;
void update1(long long x,long long d)
{
while(x<=N)
{
tree1[x] += d;
x += lowbit(x);
}
}
void update2(long long x,long long d)
{
while(x<=N)
{
tree2[x] += d;
x += lowbit(x);
}
}
long long sum1(long long x)
{
long long ans = 0;
while(x>0)
{
ans += tree1[x];
x -= lowbit(x);
}
return ans;
}
long long sum2(long long x)
{
long long ans = 0;
while(x>0)
{
ans += tree2[x];
x -= lowbit(x);
}
return ans;
}
int main()
{
cin>>n>>m;
long long old = 0,a;
for(int i = 1;i <= n;i++)
{
cin>>a;
update1(i,a-old);
update2(i,(i-1)*(a-old));
old = a;
}
while(m--)
{
long long q,l,r,d;
cin>>q;
if(q==1)
{
cin>>l>>r>>d;
update1(l,d);
update1(r+1,-d);
update2(l,d*(l-1));
update2(r+1,-d*r);
}
else
{
cin>>l>>r;
cout<<r*sum1(r) - sum2(r) - (l-1)*sum1(l-1) + sum2(l-1)<<endl;
}
}
return 0;
}
26.进制哈希
给定N个字符串,求出N个字符串中共有多少个不同的字符串
题目链接:https://www.luogu.com.cn/problem/P3370
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
unsigned long long a[10010];
string s;
unsigned long long BKDRHash(string s)
{
unsigned long long P = 131,H = 0;//P是进制,H是哈希值
int n = s.size();
for(int i = 0;i < n;i++)
{
H = H * P + s[i] - 'a' + 1;
}
return H; //隐含了取模操作
}
int main()
{
int n;
cin>>n;
for(int i = 0;i < n;i++)
{
cin>>s;
a[i] = BKDRHash(s);//将字符串s的哈希值记录在a[i]中
cout<<a[i] <<endl;
}
int ans = 0;
sort(a,a+n);
for(int i = 0;i < n;i++)
{
if(a[i]!=a[i+1])//统计有多少个不同的哈希值
{
ans++;
}
}
cout<<ans;
return 0;
}
求区间[l,r] 的哈希值的代码
unsigned long long(unsigned long long l,unsigned long long r)
{
return H[r] - H[l-1] * P[r-l+1];
}
27.KMP(在一个文本中查找一个模式串)
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
const int N = 1005;
char str[N],pattern[N];
int Next[N];
int cnt;
void getnext(char *p,int plen)
{
// 这个函数用于计算模式字符串的 next 数组。
//这个数组在 KMP 算法中非常重要,用于确定当发生不匹配时模式字符串应该回溯到哪里。
Next[0] = 0;Next[1] = 0;
for(int i = 1;i < plen;i++)
{
int j = Next[i];
while(j&&p[i]!=p[j])
j = Next[i];
if(p[i]==p[j]) Next[i+1] = j+1;
else Next[i+1] = 0;
}
}
void kmp(char *s,char *p)//在s中找p
{
int last = -1;
int slen = strlen(s),plen = strlen(p);
getnext(p,plen);
int j = 0;
for(int i = 0;i < slen;i++)
{
while(j&&s[i]!=p[j])
j = Next[j];
if(s[i]==p[j]) j++;
if(j==plen)
{
if(i-last>=plen)
{
cnt++;
last=i;
}
}
}
}
int main()
{
while(~scanf("%s",str))
{
if(str[0]=='#') break;
scanf("%s",pattern);
cnt = 0;
kmp(str,pattern);
printf("%d\n",cnt);
}
return 0;
}
/*输入:abcde a3
aaaaa aa
#
输出:0
3
*/
28.字符串--Manacher--最长回文子串
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
const int N = 11000002;
int n,P[N<<1];//P[i]代表以S[i]为中心的回文半径
char a[N],S[N<<1];
void change()
{
n = strlen(a);
int k = 0;
S[k++] = '$',S[k++] = '#';
for(int i = 0;i < n;i++)
{
S[k++] = a[i];
S[k++] = '#';
}
S[k++] = '&';//首尾不一样,确保在while循环时不越界
n = k;
}
void manacher()
{
int R = 0,C;
for(int i = 1;i < n;i++)
{
if(i < R) P[i] = min(P[(C<<1)-i],P[C]+C-i);//P[2C-i],P[C]+C-i
//这个点为i,关于C的对称点为j,就是(i+j)/2 = C,j = 2C - i;P[i] = w = R - i = C + P[C] - i;
else P[i] = 1;
while(S[i+P[i]] == S[i-P[i]]) P[i]++;//暴力,中心扩展法
if(P[i]+i>R)//更新最大R
{
R = P[i] + i;
C = i;
}
}
}
int main()
{
cin>>a;
change();
manacher();
int ans = 1;
for(int i = 0;i < n;i++)
{
ans = max(ans,P[i]);
}
cout<<ans-1;
return 0;
}
29.最短路算法
29.1Floyd最短路算法
最短路算法Floyd算法:
通过其他的点进行中转来求的两点之间的最短路,floyed不仅能求任意两点的最短路,还能求一个点能否到另一个点。不能有负环
f[0][x][y]:x 与 y 的边权,或者 0,或者 +无穷(f[0][x][y] 什么时候应该是 +无穷?当 x 与 y 间有直接相连的边的时候,为它们的边权;当 x = y 的时候为零,因为到本身的距离为零;当 x 与 y 没有直接相连的边的时候,为 +无穷)。
题目链接:1.蓝桥公园 - 蓝桥云课 (lanqiao.cn)
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
const long long INF = 0x3f3f3f3f3f3f3f3fLL;
const int N = 405;
int n,m,q;//n个点,m条边,q次查询
long long dp[N][N];
void input()
{
memset(dp,0x3f,sizeof(dp));//初始化
for(int i = 1;i <= m;i++)
{
long long u,v;
long long w;
cin>>u>>v>>w;
dp[u][v] = dp[v][u] = min(dp[u][v],w);//防止有重边
}
}
void floyd()
{
for(int k = 1;k <= n;k++)
{
for(int i = 1;i <= n;i++)
{
for(int j = 1;j <= n;j++)
{
dp[i][j] = min(dp[i][j],dp[i][k] + dp[k][j]) ;//代表不经过这条边 及经过k这条边
}
}
}
}
void output()
{
int s,t;
while(q--)
{
cin>>s>>t;
if(dp[s][t] == INF)
{
cout<<"-1"<<endl;
}
else if(s==t)
{
cout<<"0"<<endl;
}
else
{
cout<<dp[s][t]<<endl;
}
}
}
int main()
{
cin>>n>>m>>q;
input();
floyd();
output();
return 0;
}
利用bitset优化
bitset<N>d[N];
void folyd()
{
for(int k = 1;k <= n;k++)
{
for(int i = 1;i <= n;i++)
{
if(d[i][k])
d[i]|=d[k];
}
}
}
传递闭包问题
for(int k = 1;k <= n;k++)
{
for(int i = 1;i <= n;i++)
{
for(int j = 1;j <= n;j++)
{
if(dp[i][k]&&dp[k][j])
{
dp[i][j] = 1;
}
}
}
}
29.2Dijkstras算法--最短路算法
核心代码:
最短路--Dijkstra算法
核心代码:
for(int i=0; i<n; i++)//更新n个点(包括起点自身)到起点的权重
{
int t = -1;
for(int j=1; j<=n; j++) // 在没有确定最短路中的所有点找出距离最短的那个点 t
if(!st[j] && (t == -1 || dist[t] > dist[j]))
t=j;
st[t]=true; // 代表这个点已经被更新(确定与起点距离消耗的最小权重)
for(int j=1; j<=n; j++) // 用 t 更新其他点的最短距离
dist[j] = min(dist[j],dist[t]+g[t][j]);//实际上这里有很多多余步骤,因为st[]=ture的点已经不需要t来更新(已经是最小的),所以有堆优化版的dijkstra算法
}
是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题
题目链接:https://www.luogu.com.cn/problem/P2865
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 5010;//最大节点数
const int MAXM = 200010;//最大边数
const int INF = 0x3f3f3f3f;
struct Edge{
int next, to, dis;
}e[MAXM];
int head[MAXN], num, dis[MAXN][2], vis[MAXN];
//head数组用于存储每个节点的第一条边的索引num 用于记录边的数量
//dis[i][0]存储从起点到节点i 的最短路径长度
//dis[i][1] 存储次短路径长度。vis数组用于标记节点是否在队列中
inline void Add(int u, int v, int w){ //向图中添加一条边无向边
e[++num].to = v; e[num].next = head[u]; e[num].dis = w; head[u] = num;
e[++num].to = u; e[num].next = head[v]; e[num].dis = w; head[v] = num;
}
int a, b, c, now, n, m;
queue <int> q;
int main(){
cin>>n>>m;
while(m--){
cin>>a>>b>>c;
Add(a, b, c);
}
for(int i = 1; i <= n; ++i) dis[i][0] = dis[i][1] = INF;
dis[1][0] = 0; q.push(1);
while(q.size()){
now = q.front();
q.pop();
vis[now] = 0;//未访问 将当前节点标记为未访问
for(int i = head[now]; i; i = e[i].next) //意思是遍历所有邻接节点
//head[now]:当前节点;e[i].next下一节点
{
int v = e[i].to;//获取当前边指向的节点 v
#define u now
if(dis[v][0] > dis[u][0] + e[i].dis)
//如果通过当前边 u->v 可以找到比当前记录的最短路径更短的路径,
//则更新 v 的最短路径,并同时更新其次短路径为之前的最短路径
{
dis[v][1] = dis[v][0];
dis[v][0] = dis[u][0] + e[i].dis;
if(!vis[v]) q.push(v), vis[v] = 1;
}
else if(dis[v][1] > dis[u][0] + e[i].dis && dis[u][0] + e[i].dis != dis[v][0])
//当前的路径比次最短路径小,但是又不等于最短路径
{
dis[v][1] = dis[u][0] + e[i].dis;
if(!vis[v]) q.push(v), vis[v] = 1;
}
if(dis[v][1] > dis[u][1] + e[i].dis && dis[u][1] + e[i].dis != dis[v][0])
//通过当前边 u->v 可以找到比当前记录的次短路径更短,
//且不等于当前的最短路径和次短路径的路径,则再次更新 v 的次短路径
{
dis[v][1] = dis[u][1] + e[i].dis;
if(!vis[v]) q.push(v), vis[v] = 1;
}
}
}
printf("%d\n", dis[n][1]);
return 0;
}
题目链接:https://www.luogu.com.cn/problem/P4779
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
const int N = 100010,M = 500050;//点与边
const int INF = 0x3f3f3f3f;
struct Edge{
int to,dist,next;
}edge[M];
int head[N],dis[N],cnt;
bool vis[N];
int n,m,s;
inline void add_edge(int u,int v,int w)
{
cnt++;
edge[cnt].to = v;edge[cnt].dist = w;edge[cnt].next = head[u];head[u] = cnt;
}
struct node{
int dis;
int pos;
bool operator < (const node &x)const
{
return x.dis < dis;//比较当前对象的dis成员与传入对象的dis成员。
//如果传入对象的dis小于当前对象的dis,则返回true;否则返回false。
}
};
priority_queue<node>q;
inline void dijkstra()
{
dis[s] = 0;
q.push((node){0,1});
while(!q.empty())
{
node temp = q.top();
q.pop();
int x = temp.pos,d = temp.dis;
if(vis[x])
{
continue;
}
vis[x] = 1;
for(int i = head[x];i;i = edge[i].next)
{
int y = edge[i].to;
if(dis[y] > dis[x] + edge[i].dist)
{
dis[y] = dis[x] + edge[i].dist;
if(!vis[y])
{
q.push((node){dis[y],y});
}
}
}
}
}
int main()
{
cin>>n>>m>>s;
for(int i = 1;i <= n;i++)
{
dis[i] = INF;
}
for(int i = 0;i < m;i++)
{
int u,v,w;
cin>>u>>v>>w;
add_edge(u,v,w);
}
dijkstra();
for(int i = 1;i <= n;i++)
{
cout<<dis[i]<<" ";
}
return 0;
}
29.3Bellman与SPFA可以用于边权为负数的图
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
const int N = 105;
const int INF = 0x3f3f3f3f;
int n,m,cnt;
struct edge{
int u,v,w;
}e[10010];
int pre[N];
void print_path(int s,int t)//打印路径
{
if(s==t)
{
cout<<s<<" ";
return;
}
print_path(s,pre[t]);
cout<<t<<" ";
}
void bellman()
{
int s = 1;//起点
int dis[N];//代表第i个节点到起点的最短距离
for(int i = 1;i <= n;i++)
{
dis[i] = INF;//初始化
}
dis[1] = 0;
for(int k = 1;k <= n;k++)
{
for(int i = 0;i < cnt;i++)//检查几条边
{
int x = e[i].u,y = e[i].v;
if(dis[x] > dis[y]+e[i].w)
{
dis[x] = dis[y] + e[i].w;
pre[x] = y;
}
}
}
cout<<dis[n]<<endl;
//print_path(s,n);
}
int main()
{
while(~scanf("%d%d",&n,&m))
{
if(n==0&&m==0)
{
return 0;
}
cnt = 0;
while(m--)
{
int a,b,c;
cin>>a>>b>>c;
e[cnt].u = a;e[cnt].v = b;e[cnt].w = c;cnt++;
e[cnt].u = b;e[cnt].v = a;e[cnt].w = c;cnt++;
}
bellman();
}
return 0;
}
SPFA方法
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6+10,M = 2e6+10;//顶点和边的最大数量
const int INF = 0x3f3f3f3f;
int n,m;
int pre[N];//pre数组用于记录最短路径树中每个节点的父节点。
void print_path(int s,int t)//打印路径
{
if(s==t)
{
cout<<s<<" ";
return;
}
print_path(s,pre[t]);
cout<<t<<" ";
}
int head[N],cnt;//
struct {
int to,next,w;
}edge[M];
void init()
{
for(int i = 0;i < N;i++)
{
head[i] = -1;//点初始化
}
for(int i = 0;i < M;i++)
{
edge[i].next = -1;//边初始化
}
cnt = 0;
}
void addedge(int u,int v,int w)
{
edge[cnt].to = v;edge[cnt].w = w;edge[cnt].next = head[u];head[u] = cnt++;
}
int dis[N];
bool inq[N];
int Neg[N];
int spfa(int s)
{
memset(Neg,0,sizeof(Neg));
Neg[s] = 1;
for(int i = 1;i <= n;i++)
{
dis[i] = INF;
inq[i] = false;
}
dis[s] = 0;
queue<int>q;
q.push(s);
inq[s] = true;
while(!q.empty())
{
int u = q.front();
q.pop();
inq[u] = false;
for(int i = head[u];~i;i = edge[i].next)//~i----i!=-1
//遍历顶点u的所有出边
{
int v = edge[i].to,w = edge[i].w;
if(dis[u] + w <dis[v])
{
dis[v] = dis[u] + w;
pre[v] = u;
if(!inq[v])
{
inq[v] = true;
q.push(v);
Neg[v]++;
if(Neg[v] > n)//检测到负环
return 1;
}
}
}
}
return 0;//没有检测到负环
}
int main()
{
while(~scanf("%d%d",&n,&m))
{
init();
if(n==0&&m==0)
{
return 0;
}
while(m--)
{
int u,v,w;
cin>>u>>v>>w;
addedge(u,v,w);
addedge(v,u,w);
}
spfa(1);
cout<<dis[n]<<endl;
}
return 0;
}
题目链接:https://www.luogu.com.cn/problem/P3371
邻接表+spfa:单源最短路径
代码
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
const int N = 1e4+10,M = 500000+10;
const int INF = 2147483647;
struct node{
int to,next,w;
}edge[M];
int n,m,s,head[M],cnt = 0;
int dis[N],inq[N];
void addedge(int u,int v,int w)
{
edge[++cnt].to = v;edge[cnt].w = w;edge[cnt].next = head[u];head[u] = cnt;
}
void spfa()
{
queue<int>q;
for(int i = 1;i <= n;i++)
{
dis[i] = INF;
inq[i] = 0;
}
q.push(s);
dis[s] = 0;
inq[s] = 1;
while(!q.empty())
{
int u = q.front();
q.pop();
inq[u] = 0;
for(int i = head[u];i;i = edge[i].next)
{
int v = edge[i].to;
if(dis[v] > dis[u] + edge[i].w)
{
dis[v] = dis[u] + edge[i].w;
if(inq[v]==0)
{
inq[v] = 1;
q.push(v);
}
}
}
}
}
int main()
{
cin>>n>>m>>s;
while(m--)
{
int u,v,w;
cin>>u>>v>>w;
addedge(u,v,w);
//addedge(v,u,w);
}
spfa();
for(int i = 1;i <= n;i++)
{
if(s==i)
{
cout<<"0"<<" ";
}
else
{
cout<<dis[i]<<" ";
}
}
return 0;
}
题目链接:https://www.luogu.com.cn/problem/P1730
代码:
//dis[i][j][k]代表从i出发到j点,途中经过k条边的最短距离
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
int n,m,t;
int dis[55][55][1010];
int cnt;
int top[55],to[1010],len[1010],nex[1010];
void spfa(int s)
{
dis[s][s][0] = 0;
queue<pair<int,int>>q;
q.push(make_pair(s,0));
while(!q.empty())
{
int now = q.front().first;
int e = q.front().second;
q.pop();
for(int i = top[now];i;i = nex[i])
{
if(dis[s][to[i]][e+1] > dis[s][now][e] + len[i])
{
dis[s][to[i]][e+1] = dis[s][now][e] + len[i];
q.push(make_pair(to[i],e+1));
}
}
}
return;
}
int main()
{
cin>>n>>m;
memset(dis,0x3f,sizeof dis);
for(int i = 1;i <= m;i++)
{
int u,v,w;
cin>>u>>v>>w;
cnt++;
to[cnt] = v;
len[cnt] = w;
nex[cnt] = top[u];
top[u] = cnt;
}
cin>>t;
while(t--)
{
int x,y;
cin>>x>>y;
spfa(x);
double ans = INF;
for(int i = 1;i <= m;i++)
{
if(dis[x][y][i]!=INF)
{
ans=min(ans,double(dis[x][y][i])/double(i));
}
}
if(ans != INF)
{
printf("%.3f\n",ans);
}
else
{
cout<<"OMG!"<<endl;
}
}
return 0;
}
或
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
int n,m,t,cnt;
const int INF = 0x3f3f3f3f;
int head[55],dis[55][55][1010];dis[i][j][k]代表从i出发到j点,途中经过k条边的最短距离
struct Edge{
int to,dist,next;
}edge[1010];
inline void add_edge(int u,int v,int w)
{
cnt++;
edge[cnt].to = v;
edge[cnt].dist = w;
edge[cnt].next = head[u];
head[u] = cnt;
}
void spfa(int s)
{
dis[s][s][0] = 0;
queue<pair<int,int>>q;
q.push(make_pair(s,0));//起点是s,距离是0
while(!q.empty())
{
int now = q.front().first;
int diss = q.front().second;
q.pop();
for(int i = head[now];i;i = edge[i].next)
{
if(dis[s][edge[i].to][diss+1] > dis[s][now][diss] + edge[i].dist)
{
dis[s][edge[i].to][diss+1] = dis[s][now][diss] + edge[i].dist;
q.push(make_pair(edge[i].to,diss+1));
}
}
}
return;
}
int main()
{
cin>>n>>m;
memset(dis,0x3f,sizeof dis);
for(int i = 1;i <= m;i++)
{
int u,v,w;
cin>>u>>v>>w;
add_edge(u,v,w);
}
cin>>t;
while(t--)
{
int x,y;
cin>>x>>y;
spfa(x);
double ans = INF;
for(int i = 1;i <= m;i++)
{
if(dis[x][y][i]!=INF)
{
ans=min(ans,double(dis[x][y][i])/double(i));
}
}
if(ans != INF)
{
printf("%.3f\n",ans);
}
else
{
cout<<"OMG!"<<endl;
}
}
return 0;
}
30.最小生成树
30.1prim算法
prim算法基于贪心,我们每次总是选出一个离生成树距离最小的点去加入生成树,最后实现最小生成树,思路如下:
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1000,INF = 0x3f3f3f3f;//定义一个INF表示无穷大。
int g[MAXN][MAXN],dist[MAXN],n,m,res;
//我们用g[][]数组存储这个图,dist[]储存到集合S的距离,res保存结果。
bool book[MAXN];//用book数组记录某个点是否加入到集合S中。
void prim()
{
dist[1] = 0;//把点1加入集合S,点1在集合S中,将它到集合的距离初始化为0
book[1] = true;//表示点1已经加入到了S集合中
for(int i = 2 ; i <= n ;i++)dist[i] = min(dist[i],g[1][i]);//用点1去更新dist[]
for(int i = 2 ; i <= n ; i++)
{
int temp = INF;//初始化距离
int t = -1;//接下来去寻找离集合S最近的点加入到集合中,用t记录这个点的下标。
for(int j = 2 ; j <= n; j++)
{
if(!book[j]&&dist[j]<temp)//如果这个点没有加入集合S,而且这个点到集合的距离小于temp就将下标赋给t
{
temp = dist[j];//更新集合V到集合S的最小值
t = j;//把点赋给t
}
}
if(t==-1){res = INF ; return ;}
//如果t==-1,意味着在集合V找不到边连向集合S,生成树构建失败,将res赋值正无穷表示构建失败,结束函数
book[t] = true;//如果找到了这个点,就把它加入集合S
res+=dist[t];//加上这个点到集合S的距离
for(int j = 2 ; j <= n ; j++)dist[j] = min(dist[j],g[t][j]);//用新加入的点更新dist[]
}
}
int main()
{
cin>>n>>m;//读入这个图的点数n和边数m
for(int i = 1 ; i<= n ;i++)
{
for(int j = 1; j <= n ;j++)
{
g[i][j] = INF;//初始化任意两个点之间的距离为正无穷(表示这两个点之间没有边)
}
dist[i] = INF;//初始化所有点到集合S的距离都是正无穷
}
for(int i = 1; i <= m ; i++)
{
int a,b,w;
cin>>a>>b>>w;//读入a,b两个点之间的边
g[a][b] = g[b][a] = w;//由于是无向边,我们对g[a][b]和g[b][a]都要赋值
}
prim();//调用prim函数
if(res==INF)//如果res的值是正无穷,表示不能该图不能转化成一棵树,输出orz
cout<<"orz";
else
cout<<res;//否则就输出结果res
return 0;
}
题目链接:https://www.luogu.com.cn/problem/P3366
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int M = 2e5+10,N = 5010;
int n,m;
struct edge{
int to,w;
edge(int a,int b)
{
to = a,w = b;
}
};
vector<edge>G[M];
struct node{
int id,dis;//点、边
node(int a,int b)
{
id = a,dis = b;
}
bool operator < (const node &u) const
{
return dis > u.dis;
}
};
bool done[N];
void prim()//从s到其他点的最短路
{
int s = 1;//假设这个点是1
for(int i = 1;i <= N;i++)
{
done[i] = false;//初始化,所有点未被访问
}
priority_queue<node>q;
q.push(node{s,0});//从s开始处理队列
int ans = 0,cnt = 0;
while(!q.empty())
{
node u = q.top();
q.pop();
if(done[u.id]) continue;//丢弃已经在MST中的点
done[u.id] = true;
ans += u.dis;
cnt++;//统计点数
for(int i = 0;i < G[u.id].size();i++)
{
edge y = G[u.id][i];
if(done[y.to]) continue;//丢弃已经在MST中的点
q.push(node(y.to,y.w));
}
}
if(cnt==n)//注意:cnt代表点数 cout<<ans;
else cout<<"orz";
}
int main()
{
cin>>n>>m;
for(int i = 1;i <= m;i++)
{
int a,b,w;
cin>>a>>b>>w;
G[a].push_back(edge(b,w));
G[b].push_back(edge(a,w));
}
prim();
return 0;
}
30.2Kruskal算法
对边的长度贪心并加入T中。先对边长排序,sort()排序,依次将最短的边加入T
判断圈,每次加入新的边,判断他是否和已经加入T的边形成圈,,即判断连通性,最高效的办法是并查集
题目链接:https://www.luogu.com.cn/problem/P3366
= e2;
cnt++; //统计边数
}
}
if(cnt==n-1) cout<<ans; //n-1条边
else cout<<"orz";
}
int main()
{
cin>>n>>m;
for(int i = 1;i <= m;i++)
{
cin>>edge[i].u>>edge[i].v>>edge[i].w;
}
kruskal();
return 0;
}