本文将记录个人做题时常用算法模板,供大家参考。
本人为一名蒟蒻,如有错误,欢迎大佬指正!
主要内容:
1.不开long long见祖宗,因此干脆将int机械替换为long long
2.解绑&endl速度优化:解绑是为了提升大部分情况下cin和cout的速度,endl速度比直接输出\n要慢
3.定义常用数组,做题快人一步;开在main外面,初始为0。
4.应对某些OJ上题目的多组输入(如Codeforces):main内用t代表输入样例数,采用solve函数编写题解,若要跳过本组样例则直接return。
#include//懒人用万能头
#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;
}
//构造pair
pair a;
//也可以写成
pair> p;
//支持比较:按字典序
//给pair赋值
a=make_pair(3,4);
a={3,4};
//访问第一个元素
a.first;
//访问第二个元素
a.second;
来自帅哥学长的板子,方便好记。但是被隔壁大佬说拓展性不行:-( 。
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<
虽然,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);
}
这里提供两个板子:
常规判断质数——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
}
}
}
此处提供两个高效排序法,但是一般排序用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)r--;
if(l<=r)
{
swap(a[l],a[r]);
l++;r--;
}
}
if(leftl) 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];
}
快速幂板子提供两种版本。
常规运算版
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;
}
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]));
}
}
本人做过搜索的题目暂时不多,而且还喜欢用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"<
查找连通块(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<
本人尚未精通并查集,故仅提供两个基本操作。并查集原理则自行了解。
使用并查集之前,记得初始化祖宗节点数组:
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);
}
高精度加减法:模拟竖式
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
int f(int x){
if(x>1) return f(x/2)*10+x%2;
else return x%2;
}
cmath库中有专门的函数可以求对数
log() //以e为底的对数
log10() //以10为底的对数
但是要表示如以3为底的对数呢?答案是换底公式!
double log3(double x){
return log(x)/log(3);
}
CSDN有其他大佬发的更详细更全面的帖子,此处仅供参考Orz......
若不使用万能头,使用前需加入头文件:
#include
一个近似替代字符数组的容器。
//定义string,并输入
string s; cin>>s;
//string s[10]相当于二维字符串数组
//获取长度
int len=s.length();
int len=s.size();
//遍历(相当于遍历字符串数组)
for(int i=0;i
若不使用万能头,使用前需加入头文件:
#include
本人并不喜欢用vector,但是因人而异,可以将其视为可动态添加元素的数组。
//定义vector(尖括号内为数据类型)
vector a;//a相当于数组名
vector b(10);//定义一个长度为10的ve
vector c(10,1);//定义一个长度为10的ve,初始化为1
vector 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::iterator i=a.begin();i!=a.end();i++){}
//法3:auto关键字
for(auto i=a.begin();i!=a.end();i++){}
for(auto x:a){}
//vector支持比较运算,按字典序排
若不使用万能头,使用前需加入头文件:
#include
数据结构——栈,是一种先进后出的数据结构。
//栈的定义
stack a;
//常用函数
a.empty(); //判断堆栈是否为空
a.pop(); //弹出堆栈顶部的元素
a.push(); //向堆栈顶部添加元素
a.size(); //返回堆栈中元素的个数
a.top(); //返回堆栈顶部的元素
若不使用万能头,使用前需加入头文件:
#include
数据结构——队列,是一种先进先出的数据结构。
//队列是一种先进先出的数据结构
//队列的定义
queue a;
//常用函数
a.push(x); //在队尾插入一个元素
a.pop(); //删除队列第一个元素
a.size(); //返回队列中元素个数
a.empty(); //如果队列空则返回true
a.front(); //返回队列中的第一个元素
a.back(); //返回队列中最后一个元素
//拓展:优先队列:
priority_queue pq;//默认大根堆,本身具有顺序,即优先级高的元素先弹出
priority_queue,greater> pqs;//定义小根堆优先队列
pq.top(); //访问队头元素
pq.empty(); //队列是否为空
pq.size(); //返回队列内元素个数
pq.push(x); //插入元素到队尾 (并执行上浮操作 )
pq.pop(); //弹出队头元素
若不使用万能头,使用前需加入头文件:
#include
map也称映射表,对于每一个元素提供一对一的hash。
//map的定义
map mp;//尖括号内第一个为键(key)的类型,第二个为值(value)的类型
//常用函数
mp.empty();//返回是否为空
mp.clear();//清空map
mp.erase(x);//删除元素
pair 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){}
set就是集合,集合中的每个元素只出现一次,并且是排好序的(默认按键值升序排列),而multiset则可以使元素重复出现。
//set的定义(multiset定义同理)
set st;//默认升序序列
set> 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())
由于本人数学并不好,因此只能记录一下赛时遇见的一些数论。
最小公倍数由两个数的乘积除以最大公因数而得。
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);
}
本文将持续更新~(最后编辑时间2023.12.19)