NOIP复赛快到了,于是我整理了一份算法模板,以防忘记。本人弱省OI蒟蒻,若有不正确的地方请指出
1.并查集算法
//并查集基本思想:将两个独立的集合合并到一坨(莫忘鸟要判断根节点是否相同)
#include <iostream>
using namespace std;
int fa[10001]={0};
//找根节点
int findfa(int x) {
if(x==fa[x]) return x;
else {
fa[x]=findfa(fa[x]);
return fa[x];
}
}
//判断根节点是否相同
bool judge(int x,int y) {
if(findfa(x)==findfa(y)) return true;
else return false;
}
//合并两个独立的集合(合并根节点)
void join(int x,int y) {
int p1=findfa(x);
int p2=findfa(y);
if(p1!=p2) fa[p1]=p2;
}
int main() {
int n,m,z,x,y;
cin >> n >> m;
fa[1]=1;
for(int i=1;i<=n;i++) {
fa[i]=i;//每个结点的根就是自己(独立的)
}
for(int i=1;i<=m;i++) {
cin >> z >> x >> y;
switch(z) {
case 1:
join(x,y);
break;
case 2:
if(judge(x,y)) cout << "Y" << endl;
else cout << "N" << endl;
break;
default:
break;
}
}
return 0;
}
2.二分查找算法
//二分查找思想:必须从一个单调序列中选取一个中位数为基准,要找的数比基准数小/大就向左/右找
#include <iostream>
using namespace std;
int array[50001];
//从小到大二分查找
int search(int n, int target) {
int low = 1, high = n, middle;
while(low <= high) { //还冇找完
middle = (low + high)/2; //基准数
if(target == array[middle]) { //找到
return middle;
} else if(target < array[middle]) { //比此小向左找
high = middle - 1;
} else if(target > array[middle]) { //比此大向右找
low = middle + 1;
}
}
return -1; //一直找不到
}
//这是一个单调递增的序列
int main() {
int n,p;
cin >> n >> p;
for(int i=1;i<=n;i++) cin >> array[i];
cout << search(n,array[p]) << endl;
return 0;
}
3.高精度四则运算
(1)加
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
string add(string a1,string b1) {
string res;
int a[10001]={0},b[10001]={0},c[10001]={0};
int k=1;
int plus=0;
reverse(a1.begin(),a1.end());
reverse(b1.begin(),b1.end());
int lena=a1.length(),lenb=b1.length();
for(int i=0;i<lena;i++) a[i+1]=a1[i]-'0';
for(int i=0;i<lenb;i++) b[i+1]=b1[i]-'0';
while(k<=lena || k<=lenb) {
c[k]=a[k]+b[k]+plus;
plus=c[k]/10;
c[k]%=10;
k++;
}
c[k]=plus;
for(int i=k;i>=1;i--) res+=(char)(c[i]+'0');
while(res[0]=='0') res.erase(res.begin(),res.begin()+1);
if(res.empty()) return "0";
else return res;
}
int main() {
string a,b;
cin >> a >> b;
cout << add(a,b) << endl;
return 0;
}
(2)减
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
inline void strswap(string &a,string &b) {
string t;
t=a;
a=b;
b=t;
}
inline bool alessb(string a,string b) {
if(a.length()<b.length()) return true;
else if(a.length()==b.length()) {
if(a<b) return true;
else return false;
} else return false;
}
inline string minus1(string a1,string b1) {
bool negative=false;
string str;
if(alessb(a1,b1)) {
strswap(a1,b1);
negative=true;
}
int a[20001],b[20001],c[20001];
int lena=a1.length(),lenb=b1.length();
int k=1;
reverse(a1.begin(),a1.end());
reverse(b1.begin(),b1.end());
for(int i=0; i<lena; i++) a[i+1]=a1[i]-'0';
for(int i=0; i<lenb; i++) b[i+1]=b1[i]-'0';
while(k<=lena || k<=lenb) {
if(a[k]-b[k]<0) {
a[k+1]--;
a[k]+=10;
}
c[k]=a[k]-b[k];
k++;
}
for(int i=k-1; i>=1; i--) str+=(char)(c[i]+'0');
while(str[0]=='0') str.erase(str.begin(),str.begin()+1);
if(str.empty()) return "0";
else {
if(negative) return "-"+str;
else return str;
}
}
int main() {
string a,b;
cin >> a >> b;
cout << minus1(a,b) << endl;
return 0;
}
(3)乘
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
string mul(string a1,string a2) {
int a[10001]={0},b[10001]={0},c[10001]={0};
string result;
int plus=0;
reverse(a1.begin(),a1.end());
reverse(a2.begin(),a2.end());
int lena=a1.length(),lenb=a2.length();
for(int i=0;i<lena;i++) a[i+1]=a1[i]-'0';
for(int i=0;i<lenb;i++) b[i+1]=a2[i]-'0';
for(int i=1;i<=lena;i++) {
plus=0;
for(int j=1;j<=lenb;j++) {
c[i+j-1]+=(a[i]*b[j]+plus);
plus=c[i+j-1]/10;
c[i+j-1]%=10;
}
c[i+lenb]=plus;//注意每次错位相乘乘完后要进位
}
for(int i=lena+lenb;i>=1;i--) result+=((char)(c[i]+'0'));
while(result[0]=='0') result.erase(result.begin(),result.begin()+1);
if(result.empty()) return "0";
else return result;
}
int main() {
string a,b;
cin >> a >> b;
cout << mul(a,b) << endl;
return 0;
}
(4)高精除低精
//高精度除以低精度
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
typedef long long LL;
struct Info {
string result;
LL rest;
};
inline Info divide(string a1,LL d) {
LL a[20001],b[20001];
string res="";
LL rest=0;
int len=a1.length();
for(int i=0;i<len;i++) a[i+1]=a1[i]-'0';
for(int i=1;i<=len;i++) {
rest=rest*10+a[i];
b[i]=rest/d;
rest=rest%d;
}
for(int i=1;i<=len;i++) res+=(char)(b[i]+'0');
while(res[0]=='0') res.erase(res.begin(),res.begin()+1);
return {res,rest};
}
int main() {
string a;
LL b;
cin >> a >> b;
Info p = divide(a,b);
cout << p.result << "......" << p.rest;
return 0;
}
(5)高精除高精
//高精度除以高精度
#include <iostream>
#include <algorithm>
#include <string>
#pragma \
GCC optimize("O3")
using namespace std;
inline void strswap(string &a,string &b) {
string t;
t=a;
a=b;
b=t;
}
inline bool alessb(string a,string b) {
if(a.length()<b.length()) return true;
else if(a.length()==b.length()) {
if(a<b) return true;
else return false;
} else return false;
}
inline string minus1(string a1,string b1) {
bool negative=false;
string str;
if(alessb(a1,b1)) {
strswap(a1,b1);
negative=true;
}
int a[20001],b[20001],c[20001];
int lena=a1.length(),lenb=b1.length();
int k=1;
reverse(a1.begin(),a1.end());
reverse(b1.begin(),b1.end());
for(int i=0; i<lena; i++) a[i+1]=a1[i]-'0';
for(int i=0; i<lenb; i++) b[i+1]=b1[i]-'0';
while(k<=lena || k<=lenb) {
if(a[k]-b[k]<0) {
a[k+1]--;
a[k]+=10;
}
c[k]=a[k]-b[k];
k++;
}
for(int i=k-1; i>=1; i--) str+=(char)(c[i]+'0');
while(str[0]=='0') str.erase(str.begin(),str.begin()+1);//去除前导零
if(str.empty() || str=="0") return "0";
else {
if(negative) return "-"+str;
else return str;
}
}
inline string add(string a1,string b1) {
string res;
int a[10001]={0},b[10001]={0},c[10001]={0};
int k=1;
int plus=0;
reverse(a1.begin(),a1.end());
reverse(b1.begin(),b1.end());
int lena=a1.length(),lenb=b1.length();
for(int i=0;i<lena;i++) a[i+1]=a1[i]-'0';
for(int i=0;i<lenb;i++) b[i+1]=b1[i]-'0';
while(k<=lena || k<=lenb) {
c[k]=a[k]+b[k]+plus;
plus=c[k]/10;
c[k]%=10;
k++;
}
c[k]=plus;
for(int i=k;i>=1;i--) res+=(char)(c[i]+'0');
while(res[0]=='0') res.erase(res.begin(),res.begin()+1);
if(res.empty()) return "0";
else return res;
}
inline string divide(string s1,string s2) {
string cnt;
cnt=add("0","0");
while(true) {
s1=minus1(s1,s2);
if(s1[0]!='-') {
cnt=add(cnt,"1");
continue;
} else break;
}
return cnt;
}
int main() {
string s1,s2;
cin >> s1 >> s2;
cout << divide(s1,s2) << endl;
return 0;
}
4.递推算法
//动态规划 递推 maxn[i][j]=a[i][j]+max(maxn[i+1][j],maxn[i+1][j+1])
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int a[1001][1001];
int maxn[1001][1001];
int n;
int main() {
cin >> n;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
cin >> a[i][j];
memset(maxn,0,sizeof(maxn));
//最下面一行的最大值来自于自己
for(int i=1;i<=n;i++) maxn[n][i]=a[n][i];
//动态规划递推
for(int i=n-1;i>=1;i--) {
for(int j=1;j<=i;j++) {
maxn[i][j]=a[i][j]+max(maxn[i+1][j],maxn[i+1][j+1]);
}
}
cout << maxn[1][1] << endl;
return 0;
}
5.快速幂算法
//快速幂思路:将大于等于2的指数n分成两个n/2次方进行递归运算
#include <iostream>
using namespace std;
//求a的b次方快速幂
long long quickpow(int a,int b) {
//递归边界莫忘鸟
if(b==0) return 1;
else if(b==1) return a;
else {
if(b%2==0) return quickpow(a,b>>1)*quickpow(a,b>>1);
else return quickpow(a,b>>1)*quickpow(a,b>>1)*a;//注意不是偶数的指数需要再乘1次
}
}
int main() {
int a,b;
cin >> a >> b;
cout << quickpow(a,b) << endl;
return 0;
}
6.动态规划01背包问题
//基本01背包:枚举背包容量再选取最优值
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
int f[101][1001];//f[i][j]表示采药i花费时间j的最大价值
int t,m,time[101],value[101];
cin >> t >> m;
for(int i=1;i<=m;i++) {
cin >> time[i] >> value[i];
}
for(int j=1;j<=t;j++) f[0][j]=0;//初始化蛮关键(第0号药无论有多少时间花费都不能采)
for(int i=1;i<=m;i++) {
for(int j=t;j>=1;j--) {//枚举背包容量大小(采药时间限制)
if(j>=time[i]) {//没超过时间
f[i][j]=max(f[i-1][j],
f[i-1][j-time[i]]+value[i]
); //采、不采之间选一个最大价值
} else {//背包容量不够(超时)
f[i][j]=f[i-1][j];//不采
}
}
}
cout << f[m][t] << endl;//输出采药m花费时间t所得到的最大总价值
return 0;
}
7.最长上升子序列
//这其实就是个线性动规:dp[i]表示以a[i]结尾的最长上升/下降子序列的长度
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
//先求最长上升子序列,再求最长下降子序列
int main() {
int height[101], n;
int dp1[1001], dp2[1001];//dp1->最长上升子序列,dp2->最长下降子序列
cin >> n;
for (int i = 1; i <= n; i++) cin >> height[i];
for(int i=1;i<=n;i++)
dp1[i]=dp2[i]=1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j < i; j++) {
if (height[i] > height[j]) dp1[i] = max(dp1[i],dp1[j]+1);
}
}
//倒着求最长下降子序列,其实就是求最长上升子序列
for (int i = n; i >= 1; i--) {
for (int j = n; j > i; j--) {
if (height[i] > height[j]) dp2[i] = max(dp2[i], dp2[j] + 1);
}
}
int maxlen = 1;//最多留下的人
for (int i = 1; i <= n; i++) {
//减1是因为中间有个同学重复算了一次
maxlen = max(maxlen, dp1[i] + dp2[i] - 1);
}
cout << n - maxlen << endl;
return 0;
}
8.区间动规
/*基本思路:设前i到j的最优值,枚举剖分(合并)点,将(i,j)分成左右两区间,分别求左右两边最优值。状态转移方程的一般形式:F(i,j)=Max{F(i,k)+F(k+1,j)+决策,k为划分点*/
/*
对于这一题需要化环为链
我们可以用化环为链的方法,具体的实现就是将这个环的单圈复制一遍.
举个例子,输入1、2、3、4、5;那么我们就复制成1、2、3、4、5、1、2、3、4、5;
当我们用DP算完后,我们从起点起,依次向后延伸环长度,你看是不是把环的每一种情况都列举到了。
然后其实就是一个简单的DP了。
比如说我们要求合并石子i--j的最佳方案,我们可以把 i----j 分为 i--k 与 k+1--j两段;
枚举k的取值在分别取最大最小值就行了。
PS:DP状态转移式:f[i][j]=max/min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]) (最大值最小值都适用)
注:+s[j]-s[i-1]是要把i--k与k+1--j合并时的得分
*/
#include <iostream>
#include <cstring>
#define INF 99999999
using namespace std;
int Max[201][201],Min[201][201];//Max/Min[i][j]表示从第i堆石头合并到第j堆最大/小的得分
int a[201]={0};//a[i]表示前i个石头数量和
int main() {
int n;
//预处理数据
cin >> n;
for(int i=1;i<=n;i++) {
cin >> a[i];
a[i+n]=a[i];
}
//化环为链
for(int i=2;i<=2*n;i++) a[i]+=a[i-1];
for(int i=1;i<=2*n;i++)
for(int j=i+1;j<=2*n;j++)
Min[i][j]=INF;
memset(Max,0,sizeof(Max));
for(int i=2*n-1;i>=1;i--) {
for(int j=i+1;j<=2*n;j++) {//左右区间合并
for(int k=i;k<=j-1;k++) {
//Max(i,j)=max{Max(i,j),Max(i,k)+Max(k+1,j)+t[i][j]}
Max[i][j]=max(Max[i][j],Max[i][k]+Max[k+1][j]+a[j]-a[i-1]);
Min[i][j]=min(Min[i][j],Min[i][k]+Min[k+1][j]+a[j]-a[i-1]);
}
}
}
int maxn=0,minn=INF;
//还要从环上每个顶点统计一遍
for(int i=1;i<=n;i++) {
maxn=max(maxn,Max[i][i+n-1]);
minn=min(minn,Min[i][i+n-1]);
}
cout << minn << endl << maxn << endl;
return 0;
}
9.坐标规则型动规
//基本思路:那在一个矩阵中给出一些规则,然后按规则去做某些决策
//DP关系式: F(i,j)=Max{f(i-1,k)}+决策
#include <iostream>
#include <algorithm>
using namespace std;
int bx,by,mx,my;
long long f[31][31]={0};//f[i][j]表示(0,0)到(i,j)的路径条数
long md[9][2]={ {0,0},{1,2},{1,-2},{-1,2},{-1,-2},{2,1},{2,-1},{-2,1},{-2,-1}};
bool cant[31][31]={false};
int main() {
cin >> bx >> by >> mx >> my;
for(int k=0;k<9;k++) {
int nx=mx+md[k][0];
int ny=my+md[k][1];
if(nx>=0&&nx<=bx&&ny>=0&&ny<=by) cant[nx][ny]=true;
}
//起点冇被马包围
if(!cant[0][0]) {
f[0][0]=1;
for(int i=0;i<=bx;i++) {
for(int j=0;j<=by;j++) {
if(!cant[i][j]) {
if(i!=0 && !cant[i-1][j]) f[i][j]+=f[i-1][j];
if(j!=0 && !cant[i][j-1]) f[i][j]+=f[i][j-1];
}
}
}
cout << f[bx][by] << endl;
} else cout << 0 << endl;//否则冇得办法
return 0;
}
10.线段树
//使用线段树查询一个区间的最值或和差
#include <iostream>
#include <algorithm>
using namespace std;
int segtree[400001];
int array[100001];
int m,n;
//构造区间最小数的线段树
//node:结点编号,start:左区间,end:右区间
inline void build(int node,int start,int end) {
if(start==end) segtree[node]=array[start];//到了根节点填值
else {
int mid=(start+end)/2;
build(node*2,start,mid);//构造左子树
build(node*2+1,mid+1,end);//构造右子树
segtree[node]=min(segtree[node*2],segtree[node*2+1]);//从左右子树回溯填入最小值千万莫忘鸟!
}
}
//查询线段树
//node->节点编号,[begin,end]当前结点区间,[l,r]查询区间
inline int query(int node, int begin, int end, int left, int right) {
int p1, p2;
if (left > end || right < begin) return -1;//查询区间和要求的区间没有交集
if (begin >= left && end <= right) return segtree[node];//当前节点区间包含在查询区间内
/* 分别从左右子树查询,返回两者查询结果的较小值 */
p1 = query(2 * node, begin, (begin + end) / 2, left, right);
p2 = query(2 * node + 1, (begin + end) / 2 + 1, end, left, right);
/* 返回需要的值 */
if (p1 == -1) return p2;
if (p2 == -1) return p1;
return min(p1,p2);
}
int main() {
cin >> m >> n;
for(int i=1;i<=m;i++) cin >> array[i];
build(1,1,m);
for(int i=1;i<=n;i++) {
int a,b;
cin >> a >> b;
cout << query(1,1,m,a,b) << " ";
}
return 0;
}
11.邻接表
//使用vector动态数组存储边的信息
#include <iostream>
#include <vector>
using namespace std;
const int MAX = 10000;
struct EdgeNode {
int to;//指向的结点编号
int w;//这两点间的权值
};
vector<EdgeNode> v[MAX];
int main() {
EdgeNode e;
int n,m,w;//n个顶点m条边 ,w权值
int a,b;//a->b的顶点边
ios::sync_with_stdio(false);
cin >> n >> m;
for(int i=0;i<m;i++) {
cin >> a >> b >> w;
v[a].push_back({b,w});
}
//遍历
for(int i=1;i<=n;i++) {
vector<EdgeNode>::iterator it;
for(it=v[i].begin();it!=v[i].end();it++) {
EdgeNode r = *it;
cout << "Edge " << i << " to " << r.to << " is " << r.w << endl;
}
}
return 0;
}
12.Sparse Table算法
//用于求区间最值
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
int n,m;
int dp[200001][30];//dp[i][j]=[i,i+2^j-1]min
int query(int l,int r) {
int k=log(r-l+1)/log(2);//区间长度为r-l+1的对数
return max(dp[l][k],dp[r-(1<<k)+1][k]);//合并区间最小值
}
int main() {
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) {
scanf("%d",&dp[i][0]);
}
for(int i=1;i<=log(n)/log(2);i++) {//枚举log2(n)的数
for(int j=1;j<=n-(1<<i)+1;j++) {//枚举1~n-(2^i-1)的数
dp[j][i]=max(dp[j][i-1],dp[j+(1<<(i-1))][i-1]);
}
}
for(int i=1;i<=m;i++) {
int l,r;
scanf("%d%d",&l,&r);
printf("%d\n",query(l,r));
}
return 0;
}
13.SPFA(Bellman-Ford队列优化)
//注意要用邻接表实现,每次取未经过的点放入队首松弛以求最短路径
#include <iostream>
#include <fstream>
#include <algorithm>
#include <queue>
#include <vector>
#define FSTREAM 1
using namespace std;
struct Edge{
int u,v,w;
};
const int inf = 1<<30;
int n,m;
queue<int> q;
vector<Edge> e;
int dis[10001];//dis[i]->1~i的权值
bool book[10001];//i号顶点是否在队列中
int first[10001],nxt[10001];//邻接表
int main() {
#if FSTREAM
ifstream fin("spfa.in");
ofstream fout("spfa.out");
fin >> n >> m;
#else
cin >> n >> m;
#endif
fill(dis,dis+n+1,inf);
dis[1]=0;
fill(book,book+n+1,false);
fill(first,first+n+1,-1);
for(int i=0;i<m;i++) {
int p1,p2,p3;
#if FSTREAM
fin >> p1 >> p2 >> p3;
#else
cin >> p1 >> p2 >> p3;
#endif
e.push_back({p1,p2,p3});
nxt[i]=first[p1];
first[p1]=i;
}
//1号顶点入队
q.push(1);
book[1]=true;
int k;//当前需要处理的队首顶点
while(!q.empty()) {
k=first[q.front()];
while(k!=-1) {//搜索当前顶点所有边
if(dis[e[k].v]>dis[e[k].u]+e[k].w) {
dis[e[k].v]=dis[e[k].u]+e[k].w;//松弛
if(!book[e[k].v]) {//不在队列中,加入队列
book[e[k].v]=true;
q.push(e[k].v);
}
}
k=nxt[k];//继续下一个
}
book[q.front()]=false;
q.pop();
}
#if FSTREAM
for(int i=1;i<=n;i++) {
if(dis[i]!=inf) fout << dis[i] << " ";
else fout << "INF ";
}
fin.close();
fout.close();
#else
for(int i=1;i<=n;i++) {
if(dis[i]!=inf) cout << dis[i] << " ";
else cout << "INF ";
}
#endif
return 0;
}
14.埃氏筛素数
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
const int MAX = 100001;
bool prime[MAX]={false};
inline void make_prime() {
fill(prime,prime+MAX,true);
prime[0]=prime[1]=false;
int t=(int)sqrt(MAX*1.0);
for(register int i=2;i<=t;i++) {
if(prime[i]) {
for(register int j=2*i;j<MAX;j+=i) {
prime[j]=false;
}
}
}
}
int main() {
make_prime();
for(register int i=0;i<=MAX;i++) if(prime[i]) cout << i << "\t";
return 0;
}
15.欧拉筛素数
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int MAX = 100001;
bool prime[MAX];
vector<int> v;