本文将记录个人做题时常用算法模板,供大家参考。
本人为一名蒟蒻,如有错误,欢迎大佬指正!
一、杂项
1.代码模板
主要内容:
1.不开long long见祖宗,因此干脆将int机械替换为long long
2.解绑&endl速度优化:解绑是为了提升大部分情况下cin和cout的速度,endl速度比直接输出\n要慢
3.定义常用数组,做题快人一步;开在main外面,初始为0。
4.应对某些OJ上题目的多组输入(如Codeforces):main内用t代表输入样例数,采用solve函数编写题解,若要跳过本组样例则直接return。
#include<bits/stdc++.h>//懒人用万能头
#define int long long
#define endl '\n'
using namespace std;
const int N=3e6+5;
int a[N];
void solve(){
}
signed main(){
ios::sync_with_stdio(0); //解绑
cin.tie(0), cout.tie(0); //解绑
int t=1;
//cin>>t;
while(t--) solve();
system("pause");
return 0;
}
2.Pair定义与使用
//构造pair
pair<int,int> a;
//也可以写成
pair<int,pair<int,int>> p;
//支持比较:按字典序
//给pair赋值
a=make_pair(3,4);
a={3,4};
//访问第一个元素
a.first;
//访问第二个元素
a.second;
二、算法
1.二分板子
来自帅哥学长的板子,方便好记。但是被隔壁大佬说拓展性不行:-( 。
int l=1,r=1e9,mid;
int ans=0;
while(l<=r){
mid=(l+r)/2;
if(check(mid)){
ans=mid;//利用ans去记录合法值
l=mid+1;
}
else r=mid-1;
}
cout<<ans<<endl;
2.求最大公因数(GCD)与最小公倍数(LCM)
虽然,C++自带了GCD与LCM函数,但是写写也无妨。
//GCD函数递归法
int gcd(int a,int b){
if(b==0) return a;
return gcd(b,a%b);
}
//GCD异或法
int gcd(int a,int b){
while(a^=b^=a^=b%=a);
return b;
}
//求最小公倍数(lcm)即两数乘积除以最大公因数(gcd)
int lcm(int a,int b){
return a*b/gcd(a,b);
}
3.求质数
这里提供两个板子:
常规判断质数——isprime函数
//常规素数判断
int isprime(int x)
{
if(x==1||x%2==0&&x!=2)return 0;
for(int i=3;i<=sqrt(x);i+=2)//注意要等于sqrt(x)
{
if(x%i==0) return 0;
}
return 1;
}
线性质数筛——欧拉筛
//原理:通过标记一个数的倍数,后进行遍历,没被标记的则为质数
int n=1e6;//假设求1e6内所有质数
bool a[N];//用于标记是否被筛除
int prime[N],pi=0;//prime用于存放素数,pi为prime数组的专属下标
void Eulerprime(int n){
for(int i=2;i<=n;++i){
if(!a[i]) prime[++pi]=i; //++为先加一再用,先记录2,后记录质数
for(int j=1;prime[j]*i<=n;++j){
a[prime[j]*i]=1;//标记合法范围内,某一较小质数的所有倍数
if(i%prime[j]==0) break;//当取模为0的时候,则表示已经为较小的质数,则break
}
}
}
4.排序
此处提供两个高效排序法,但是一般排序用sort就行。
快速排序
平均时间复杂度O(),最坏情况下为O(
)
void quick_sort(int *a,int left,int right)
{
int key,l=left,r=right;
key=a[(l+r)/2];
while(l<=r)
{
while(a[l]<key)l++;
while(a[r]>key)r--;
if(l<=r)
{
swap(a[l],a[r]);
l++;r--;
}
}
if(left<r) quick_sort(a,left,r);
if(right>l) quick_sort(a,l,right);
}
归并排序
时间复杂度均为O()。
void msort(int *a,int *b,int l,int r){
if(l>=r) return;
int mid=(l+r)>>1;
msort(a,b,l,mid);
msort(a,b,mid+1,r);
int i=l,j=mid+1,k=l;
while(i<=mid&&j<=r){
if(a[i]>a[j]) b[k++]=a[j++];
else b[k++]=a[i++];
}
while(i<=mid) b[k++]=a[i++];
while(j<=r) b[k++]=a[j++];
for(int i=l;i<=r;i++) a[i]=b[i];
}
5.快速幂
快速幂板子提供两种版本。
常规运算版
long long Mod=1e9+7;
long long fastPower(long long base, long long power) {
long long result = 1;
while (power > 0) {
if (power % 2 == 1) {
result = result * base % Mod;
}
power = power / 2;
base = (base * base) % Mod;
}
return result;
}
位运算版
long long fastPower(long long base, long long power) {
long long result = 1;
while (power > 0) {
if (power & 1) {//此处等价于if(power%2==1)
result = result * base % Mod;
}
power >>= 1;//此处等价于power=power/2
base = (base * base) % Mod;
}
return result;
}
6.背包问题动态规划
01背包(物品有限个)
for(int i=1;i<=n;i++){
for(int j=m;j>=w[i];j--){
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);//状态转移方程
}
}
完全背包(物品无限个)
for(int i=1;i<=m;i++){
for(int j=w[i];j<=t;j++){//此时则为正序遍历
dp[j]=max(dp[j],(dp[j-w[i]]+v[i]));
}
}
7.搜索
本人做过搜索的题目暂时不多,而且还喜欢用DFS(深度优先搜索),BFS(广度优先搜索)尚不精通,因而这里仅提供两种经典题型:迷宫寻路&查找连通块
迷宫寻路(回溯法+DFS)
//模板题:洛谷B3625 迷宫寻路
int a[110][110],v[110][110];//a数组记录地图,v数组记录是否走过
int n,m;
//dx,dy两个数组用于表示走的方向
int dx[4]={0,1,0,-1};
int dy[4]={1,0,-1,0};
int flag=0;//flag用于标记可以到达
void dfs(int x,int y){
if(x<1||y<1||x>n||y>m||v[x][y]==1||a[x][y]==1) return;//防止越界、走重复、走到障碍物上
//找到就返回
if(x==n&&y==m){
flag=1;return;
}
//走过就标记
v[x][y]=1;
//四个方向试探
for(int i=0;i<=3;i++){
int tx=x+dx[i];
int ty=y+dy[i];
dfs(tx,ty);
}
}
void solve(){
cin>>n>>m;
char x;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>x;
if(x=='#') a[i][j]=1;
}
}
//从起点开始
dfs(1,1);
//判断是否能到达
if(flag==1) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
查找连通块(DFS)
//洛谷P1596 Lake Counting S
//定义8个方向
int dx[8]={1,0,1,0,-1,-1,1,-1};
int dy[8]={1,1,0,-1,0,-1,-1,1};
int ans=0;
//遇到联通的就试探
void dfs(int x,int y){
v[x][y]=1;
int tx,ty;
for(int i=0;i<=7;i++){
tx=x+dx[i];
ty=y+dy[i];
if(x>n||y>m||x<1||y<1){
continue;//越界就跳过
}
else if(a[tx][ty]==1&&v[tx][ty]==0){
dfs(tx,ty);
}
}
}
void solve(){
cin>>n>>m;
char x;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>x;
if(x=='W') a[i][j]=1;
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(a[i][j]==1&&v[i][j]==0){
dfs(i,j);//开始递归
ans++;//每递归完成一次表明有一个连通块
}
}
}
cout<<ans<<endl;
}
8.并查集
本人尚未精通并查集,故仅提供两个基本操作。并查集原理则自行了解。
使用并查集之前,记得初始化祖宗节点数组:
int fa[N];//父节点数组,记录每个点的父节点
//父节点的初始化,初始状态全部为其本身(尚未合并)
void begin(int n){
for(int i=1;i<=n;i++){
fa[i]=i;
}
}
Find
int find(int i){
if(i!=fa[i]) fa[i]=find(fa[i]);//路径压缩
return fa[i];
}
Union
void Union(int a,int b){
fa[find(a)]=find(b);
}
三、计算策略
1.高精度
高精度加减法:模拟竖式
lc=(la>lb)?la:lb;
for(i=0;i<=lc;i++){
c[i]=a[i]+b[i]+n;
n=c[i]/10;
c[i]%=10;
}
高精度乘除:分步取模
大数相乘或者相除记得有个公式:(a * b) % p = (a % p * b % p) % p
//例:判断是否能被能被603整除
for(int i=0;i<n;i++){
mod=(mod*10+(s[i]-'0'))%603;
}
if(mod==0){
cout<<"YES"<<endl;
}else{
cout<<"NO"<<endl;
}
2.十进制数转二进制数
int f(int x){
if(x>1) return f(x/2)*10+x%2;
else return x%2;
}
3.求对数
cmath库中有专门的函数可以求对数
log() //以e为底的对数
log10() //以10为底的对数
但是要表示如以3为底的对数呢?答案是换底公式!
double log3(double x){
return log(x)/log(3);
}
四、STL容器
优快云有其他大佬发的更详细更全面的帖子,此处仅供参考Orz......
1.Srting
若不使用万能头,使用前需加入头文件:
#include<string>
一个近似替代字符数组的容器。
//定义string,并输入
string s; cin>>s;
//string s[10]相当于二维字符串数组
//获取长度
int len=s.length();
int len=s.size();
//遍历(相当于遍历字符串数组)
for(int i=0;i<len;i++){
s[i]='1';
}
//比较:可以直接用大于小于号,比较字典序(没有前导零的情况下可用于高精数比较)
//添加字符or添加字符串可以直接用加号
//字符or字符串查找
string str;
s.find(str);//s为被找字符串,str为目标字符串,找不到返回-1!
s.rfind(str);//从末尾开始找
//按字典序排序
sort(s.begin(),s.end());
//字符串去重
unique(s.begin(),s.end());//unique函数用于将字符串中重复的元素自动放到字符串末尾,返回不重复子段的最后一个下标
s.erase(unique(s.begin(),s.end()),s.end());//加上erase函数才能真正实现去重的目的
//字符串截取
s.substr(1);//返回从1开始到字符串末尾的子串
s.substr(1,10);//返回从1开始到10的子串
//返回字符串首地址(用printf输出的时候)
printf("%s",s.c_str());
2.Vector
若不使用万能头,使用前需加入头文件:
#include<vector>
本人并不喜欢用vector,但是因人而异,可以将其视为可动态添加元素的数组。
//定义vector(尖括号内为数据类型)
vector<int> a;//a相当于数组名
vector<int> b(10);//定义一个长度为10的ve
vector<int> c(10,1);//定义一个长度为10的ve,初始化为1
vector<int> d[10];//相当于二维vector数组,或者为10个vector
//数据输入
int x;cin>>x;
a.push_back(x);
//常用函数
a.back();//返回最后一个数
a.front();//返回第一个数
a.clear();//清空
a.empty();//判断是否为空
a.size();//返回容器内元素个数
a.erase(a.begin()+1,a.begin()+3);//删除函数,只写一个参数即为删除那个下标的元素,写两个则删除一段区间的元素
//排序
sort(a.begin(),a.end());
//查找元素,返回下标
find(a.begin(),a.end(),10);
//vector的遍历
//法1:类似数组直接下标法
for(int i=0;i<a.size();i++){}
//法2:迭代器(类似于c语言里的指针)
for(vector<int>::iterator i=a.begin();i!=a.end();i++){}
//法3:auto关键字
for(auto i=a.begin();i!=a.end();i++){}
for(auto x:a){}
//vector支持比较运算,按字典序排
3.Stack
若不使用万能头,使用前需加入头文件:
#include<stack>
数据结构——栈,是一种先进后出的数据结构。
//栈的定义
stack<int> a;
//常用函数
a.empty(); //判断堆栈是否为空
a.pop(); //弹出堆栈顶部的元素
a.push(); //向堆栈顶部添加元素
a.size(); //返回堆栈中元素的个数
a.top(); //返回堆栈顶部的元素
4.Queue
若不使用万能头,使用前需加入头文件:
#include<queue>
数据结构——队列,是一种先进先出的数据结构。
//队列是一种先进先出的数据结构
//队列的定义
queue<int> a;
//常用函数
a.push(x); //在队尾插入一个元素
a.pop(); //删除队列第一个元素
a.size(); //返回队列中元素个数
a.empty(); //如果队列空则返回true
a.front(); //返回队列中的第一个元素
a.back(); //返回队列中最后一个元素
//拓展:优先队列:
priority_queue<int> pq;//默认大根堆,本身具有顺序,即优先级高的元素先弹出
priority_queue<int,vector<int>,greater<int>> pqs;//定义小根堆优先队列
pq.top(); //访问队头元素
pq.empty(); //队列是否为空
pq.size(); //返回队列内元素个数
pq.push(x); //插入元素到队尾 (并执行上浮操作 )
pq.pop(); //弹出队头元素
5.Map
若不使用万能头,使用前需加入头文件:
#include<map>
map也称映射表,对于每一个元素提供一对一的hash。
//map的定义
map<int,int> mp;//尖括号内第一个为键(key)的类型,第二个为值(value)的类型
//常用函数
mp.empty();//返回是否为空
mp.clear();//清空map
mp.erase(x);//删除元素
pair<int,int> xx;
mp.insert(xx);//map的插入一般是插入一个pair
/*
mp.first; //访问键
mp.sceond; //访问值
*/
//如数组一般直接使用map,例如添加映射关系
mp[x]=1;
//遍历map
for(auto it=mp.begin();it!=mp.end();it++){}
for(auto it:mp){}
6.Set&Multiset
set就是集合,集合中的每个元素只出现一次,并且是排好序的(默认按键值升序排列),而multiset则可以使元素重复出现。
//set的定义(multiset定义同理)
set<int> st;//默认升序序列
set<int,greater<int>> p;//降序序列
//set的常用函数
st.begin(); //返回第一个迭代器
st.end(); //返回末尾的迭代器
st.insert(x); //向集合插入一个元素
st.size(); //返回集合中元素个数
st.empty(); //如果集合空则返回true
st.clear(); //清空集合
st.find(x); //查找某一个数字
st.count(x); //返回multiset内某一元素个数
/* 对于删除操作
1.输入一个数x,删除所有x
2.输入一个迭代器,删除这个迭代器 */
st.erase(x);
st.lower_bound(x); //返回大于等于x的最小值的迭代器(不存在则返回end())
st.upper_bound(x); //返回大于x的最小值的迭代器(不存在则返回end())
五、数论全靠猜!
由于本人数学并不好,因此只能记录一下赛时遇见的一些数论。
1 .GCD与LCM
最小公倍数由两个数的乘积除以最大公因数而得。
int gcd(int a,int b){
if(b==0) return a;
return gcd(b,a%b);
}
//求最小公倍数(lcm)即两数乘积除以最大公因数(gcd)
int lcm(int a,int b){
return a*b/gcd(a,b);
}
2.n进制数除以n-1的余数
本文将持续更新~(最后编辑时间2023.12.19)