部分算法模板(难度:未知~普及-)
前言:部分自己打的,大部分是题解(毕竟我的码风太丑)。不确保所有模板的正确性——以后会一个个试的。
1.高精度 || 难度:普及-
(1)高精度加法
题意:求A+B
#include<cstdio>
#include<cstring>
#define MAX 500
char str[MAX];
int c[MAX],a[MAX],b[MAX],lena,lenb,len,i;
int main(){
/*这一部分,从第10行至第23行,为倒置存入的过程*/
/*输入第一个加数*/
scanf("%s",str);{//数组名本身就是一个地址, &表示取地址
lena=strlen(str);//计算字符串长度的函数
}
for(i=0;i<lena;i++){
a[i]=str[lena-1-i]-'0';//ASCII表 :'9'-'0'=9
}
/*输入第二个加数*/
scanf("%s",str);
lenb=strlen(str);
for(int i=0;i<lenb;i++){
b[i]=str[lenb-1-i]-'0';
}
/*进行加法*/
//求长度
len=(lena>lenb)?lena:lenb;//求出得数最接近的长度
/*算法核心*/
for(i=0;i<len;i++){
c[i]+=a[i]+b[i];//本位的结果
c[i+1]=c[i]/10;//判断是否需要进位
c[i]=c[i]%10;//留下进位后需要的部分
}
if(c[i]){
len++;
}//最后一位是否需要进位;
for(i=0;i<len;i++){
printf("%d",c[len-1-i]);
}//倒序输出
return 0;
}
(2)高精度减法
题意:求A-B
#include<iostream>
using namespace std;
char a[10100],b[10100],ans[10100];
int slen(char*x){
int l=0;
while(x[l++]);
return --l;
}
bool strcmp(char*x,char*y){
int xl=slen(x),yl=slen(y);
if(xl==yl){
for(int i=0;i<xl;i++){
if(x[i]>y[i])return 1;
else if(x[i]<y[i])return 0;
}
}
else return xl>yl;
}
bool check(int*ansi,int cnt){
for(int i=0;i<cnt;i++){
if(ansi[i])return 0;
}
return 1;
}
void jf(char*x,char*y,bool cmp){
bool flag1=0,flag2=0;
int tmp=0;
int xl=slen(x),yl=slen(y);
int ai[10100]={},bi[10100]={},ansi[10100]={};
for(int i=0;i<xl;i++)ai[i]=x[xl-1-i]-'0';
for(int i=0;i<yl;i++)bi[i]=y[yl-1-i]-'0';
for(int i=0;i<xl;i++){
if(ai[i]<bi[i]){
ansi[i]=ai[i]+10-bi[i];
ai[i+1]--;
}else ansi[i]=ai[i]-bi[i];
}
if(check(ansi,xl)){
ans[0]='0';
return;
}
if(cmp){
for(int i=0;i<xl;i++){
if(ansi[xl-1-i]==0&&!flag1)tmp++;
if(ansi[xl-1-i])flag1=1;
if(flag1)ans[i-tmp]=ansi[xl-1-i]+'0';
}
}else{
ans[0]='-'
for(int i=1;i<xl+1;i++){
if(!ansi[xl-i]&&!flag1)tmp++;
if(ansi[xl-i])flag1=1;
if(flag1)ans[i-tmp]=ansi[xl-i]+'0';
}
}
}
int main(){
cin>>a>>b;
if(strcmp(a,b)){
jf(a,b,1);
}else jf(b,a,0);
for(int i=0;i<slen(a)+5;i++){
cout<<ans[i];
}
}
(3)高精度除法
题意:求A/B
#include<iostream>
#include<cstring>
#define max(x,y) (x>y)?x:y
#define min(x,y) (x>y)?y:x
using namespace std;
long long a,b,c,n;
long long s[10005],s2[10005];
string str;
int main(){
cin>>str>>b;//cin>>c>>a;
for(int i=1;i<=str.size();i++){
s[i]=str[i-1]-'0';
}
for(int i=1;i<=str.size();i++){
s2[i]=(c*10+s[i])/b;
c=(c*10+s[i])%b;
}
n=1;
for (int i=1;s2[n]==0&&str.size()>n;i++){
n++;
}
for(int i=n;i<=str.size();i++){
cout<<s2[i];
}
return 0;
}
(4)高精度乘法
题意:求A*B
#include<iostream>
#include<cstring>
using namespace std;
char a1[20000],b1[20000];
int a[20000],b[20000],c[50000],i,j,len;
int main(){
//输入两个因数
cin>>a1>>b1;
a[0]=strlen(a1);
b[0]=strlen(b1);
for(i=1;i<=a[0];i++){
a[i]=a1[a[0]-i]-'0';
}
for(i=1;i<=b[0];i++){
b[i]=b1[b[0]-i]-'0';
}
/*算乘法,c数组中的每个数的值算出来*/
for(i=1;i<=a[0];i++){
for(j=1;j<=b[0];j++){
c[i+j-1]+=a[i]*b[j];
}
}
len=a[0]+b[0];
/*进位*/
for(i=1;i<len;i++){
if(c[i]>9){
c[i+1]+=c[i]/10;
c[i]%=10;
}
}
while(c[len]==0&&len>1)len--;//去掉不满足位
/*输出*/
for(i=len;i>=1;i--){
cout<<c[i];
}
return 0;
}
2.并查集 || 难度:普及-
题意:第一行包含两个整数 N , M N,M N,M ,表示共有 N N N 个元素和 M M M 个操作。
接下来 M M M 行,每行包含三个整数 Z i , X i , Y i Z_i,X_i,Y_i Zi,Xi,Yi。
当 Z i = 1 Z_i=1 Zi=1 时,将 X i X_i Xi 与 Y i Y_i Yi 所在的集合合并。
当 Z i = 2 Z_i=2 Zi=2 时,输出 X i X_i Xi 与 Y i Y_i Yi 是否在同一集合内,是的输出 Y Y Y ;否则输出 N N N 。
分析:
并查集的单次查询理想复杂度应该是 O ( l o g n ) O(log\ n) O(log n) 的,但是如果有一个这样的数据,并查集的复杂度就是 O ( n ) O(n) O(n) 了
为了避免这种情况,我们需对路径进行压缩。
即当我们经过找到祖先节点后,回溯的时候顺便将它的子孙节点都直接指向祖先,使以后的查找复杂度变回
O
(
l
o
g
n
)
O(log\ n)
O(log n) 甚至更低。
#include<iostream>
using namespace std;
int n,m,x,y,z;
int fax,fay,fa[10005];
int found(int x){
if(fa[x]==x) return x;
return fa[x]=found(fa[x]);
}
int main(){
cin>>n>>m;
for (int i=1;i<=n;i++){
fa[i]=i;
}
for (int i=1;i<=m;i++){
cin>>z>>x>>y;
fax=found(x);
fay=found(y);
if(z==1){
if(fax!=fay){
fa[fax]=fay;
}
}
else{
if(fax==fay) cout<<"Y\n";
else cout<<"N\n";
}
}
return 0;
}
3.快速幂 || 难度:普及-
题意:给你三个整数
a
,
b
,
p
a,b,p
a,b,p,求
a
b
m
o
d
p
a^b \bmod p
abmodp。
输出一行一个字符串 a^b mod p=s
。
好题解.
#include<iostream>
using namespace std;
long long a,b,p;
long long ans,tzy,tzy2;//替罪羊
int main(){
cin>>a>>b>>p;
ans=1%p;
tzy=a;tzy2=b;
while(b){
if(b%2!=0){
ans=(ans*a)%p;
}
a=(a*a)%p;
b/=2;//b>>=2;
}
cout<<tzy<<"^"<<tzy2<<" mod "<<p<<"="<<ans<<"\n";
return 0;
}
4.线性筛素数 || 难度:普及-
题意:第一行包含两个正整数
n
,
q
n,q
n,q,分别表示查询的范围和查询的个数。
接下来
q
q
q 行每行一个正整数
k
k
k,表示查询第
k
k
k 小的素数。
n
=
1
0
8
n=10^8
n=108
,
1
≤
q
≤
1
0
6
1 \le q \le 10^6
1≤q≤106
#include<iostream>
#includehttps://www.luogu.com.cn/problem/P3383<cstdio>
using namespace std;
int n,q,k,a[100000005],b[100000005];
int t;
template <typename T> void read(T &x){
x = 0;
bool f = 0;
char c = getchar();
while (c < '0' || c > '9') f |= c == '-', c = getchar();
while (c >= '0' && c <= '9') x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
if (f) x = -x;
}
template <typename T> void write(T x){
if (x < 0) putchar('-'), x = -x;
if (x < 10) putchar(x + '0');
else write(x / 10), putchar(x % 10 + '0');
}
int main(){
read(n),read(q);
a[0]=a[1]=1;
for(int i=2;i<=n;i++){
if(!a[i]){
++t;
b[t]=i;
}
for(int j=1;j<=t&&i*b[j]<=n;j++){
a[b[j]*i]=1;
if(i%b[j]==0){
break;
}
}
}
for(int i=1;i<=q;i++){
read(k);
write(b[k]);
cout<<endl;
}
return 0;
}
5.快速排序 || 难度:普及-
题意:利用快速排序算法将读入的 N N N 个数从小到大排序后输出。
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
int n,a[1000001];
using namespace std;
void qsort(int l,int r){//应用二分思想
int mid=a[(l+r)/2];//中间数
int i=l,j=r;
do{
while(a[i]<mid) i++;//查找左半部分比中间数大的数
while(a[j]>mid) j--;//查找右半部分比中间数小的数
if(i<=j){//如果有一组不满足排序条件(左小右大)的数
swap(a[i],a[j]);//交换
i++;
j--;
}
}
while(i<=j);//这里注意要有=
if(l<j) qsort(l,j);//递归搜索左半部分
if(i<r) qsort(i,r);//递归搜索右半部分
}
int main(){
cin>>n;
for(int i=1;i<=n;i++) {
cin>>a[i];
}
qsort(1,n);
for(int i=1;i<=n;i++) {
cout<<a[i]<<" ";
}
return 0;
}
6.堆 || 难度:普及-
题意:
第一行是一个整数,表示操作的次数
n
n
n。
接下来
n
n
n 行,每行表示一次操作。每行首先有一个整数
o
p
op
op 表示操作类型。
- 若 o p = 1 op = 1 op=1,则后面有一个整数 x x x,表示要将 x x x 加入数列。
- 若 o p = 2 op = 2 op=2,则表示要求输出数列中的最小数。
- 若 o p = 3 op = 3 op=3,则表示删除数列中的最小数。如果有多个数最小,只删除 1 1 1 个。
来一张图了解一下堆(这里是小根堆):
不难看出,对于堆的每个子树,它同样也是一个堆(因为是完全二叉树嘛)
1.插入:
来几张图感性理解亿下:
事实上堆的插入就是把新的元素放到堆底,然后检查它是否符合堆的性质,如果符合就丢在那里了,如果不符合,那就和它的父亲交换一下,一直交换交换交换,直到符合堆的性质,那么就插入完成了。
2.删除
再来几张图感性理解亿下?
我们要保证删除后这一整个堆还是个完好的小根堆
首先在它的两个儿子里面,找一个比较小的,和它交换一下,但是还是没法删除,因为下方还有节点,那就继续交换。
以上内容选自:这篇题解
(1)STL版
#include<iostream>
#include<queue>
using namespace std;
priority_queue<int,vector<int>,greater<int> > p;
int n,op,x;
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>op;
if(op==1){
cin>>x;
p.push(x);
}
else if(op==2){
cout<<p.top()<<"\n";
}
else if(op==3) p.pop();
}
return 0;
}
(2)手写堆版
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=1000005;
struct Myqueue{
long long last=0,a[MAXN];
Myqueue(){a[1]=0; for(int i=2;i<=1000000;i++) a[i]=1e12;}
inline int top(){return a[1];}
void push(int x){
a[++last]=x;
int tmp=last;
while(a[tmp]<a[tmp>>1])
swap(a[tmp],a[tmp>>1]),tmp>>=1;
}
void pop(){
a[1]=1e12;
swap(a[1],a[last]);
int tmp=1;
while(a[tmp]>min(a[(tmp<<1)],a[(tmp<<1)+1])){
if(a[(tmp<<1)]<a[(tmp<<1)+1]) swap(a[tmp],a[tmp<<1]),tmp<<=1;
else swap(a[tmp],a[(tmp<<1)+1]),tmp=(tmp<<1)+1;
}
last--;
}
};
Myqueue q;
int main(){
int T,op; scanf("%lld",&T);
long long x;
while(T--){
scanf("%lld",&op);
if(op==1) scanf("%lld",&x),q.push(x);
if(op==2) printf("%lld\n",q.top());
if(op==3) q.pop();
}
return 0;
}
(1)自然溢出哈希
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef unsigned long long ull;
ull base=131;
ull a[10010];
char s[10010];
int n,ans=1;
ull hashs(char s[]){
int len=strlen(s);
ull ans=0;
for (int i=0;i<len;i++)
ans=ans*base+(ull)s[i];
return ans&0x7fffffff;
}
main(){
scanf("%d",&n);
for (int i=1;i<=n;i++){
scanf("%s",s);
a[i]=hashs(s);
}
sort(a+1,a+n+1);
for (int i=2;i<=n;i++)
if (a[i]!=a[i-1])
ans++;
printf("%d\n",ans);
}
字符串哈希 || 难度:普及-
题意或许放不了,直接上板子!
(2)单模数哈希
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef unsigned long long ull;
ull base=131;
ull a[10010];
char s[10010];
int n,ans=1;
ull mod=19260817;
ull hashs(char s[]){
int len=strlen(s);
ull ans=0;
for (int i=0;i<len;i++)
ans=(ans*base+(ull)s[i])%mod;
return ans;
}
main(){
scanf("%d",&n);
for (int i=1;i<=n;i++){
scanf("%s",s);
a[i]=hashs(s);
}
sort(a+1,a+n+1);
for (int i=2;i<=n;i++)
if (a[i]!=a[i-1])
ans++;
printf("%d\n",ans);
}
(3)双模数哈希
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef unsigned long long ull;
ull base=131;
struct data{
ull x,y;
}a[10010];
char s[10010];
int n,ans=1;
ull mod1=19260817;
ull mod2=19660813;
ull hash1(char s[]){
int len=strlen(s);
ull ans=0;
for (int i=0;i<len;i++)
ans=(ans*base+(ull)s[i])%mod1;
return ans;
}
ull hash2(char s[]){
int len=strlen(s);
ull ans=0;
for (int i=0;i<len;i++)
ans=(ans*base+(ull)s[i])%mod2;
return ans;
}
bool comp(data a,data b){
return a.x<b.x;
}
main(){
scanf("%d",&n);
for (int i=1;i<=n;i++){
scanf("%s",s);
a[i].x=hash1(s);
a[i].y=hash2(s);
}
sort(a+1,a+n+1,comp);
for (int i=2;i<=n;i++)
if (a[i].x!=a[i-1].x || a[i-1].y!=a[i].y)
ans++;
printf("%d\n",ans);
}
(4)只用一个 1 0 18 10^{18} 1018质数的哈希
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef unsigned long long ull;
ull base=131;
ull a[10010];
char s[10010];
int n,ans=1;
ull mod=212370440130137957ll;//是质数!!
ull hashs(char s[]){
int len=strlen(s);
ull ans=0;
for (int i=0;i<len;i++)
ans=(ans*base+(ull)s[i])%mod;
return ans;
}
main(){
scanf("%d",&n);
for (int i=1;i<=n;i++){
scanf("%s",s);
a[i]=hashs(s);
}
sort(a+1,a+n+1);
for (int i=2;i<=n;i++)
if (a[i]!=a[i-1])
ans++;
printf("%d\n",ans);
}
8.最小生成树 || 难度:普及-
题意:
第一行包含两个整数
N
,
M
N,M
N,M,表示该图共有
N
N
N 个结点和
M
M
M 条无向边。接下来
M
M
M 行每行包含三个整数
X
i
,
Y
i
,
Z
i
X_i,Y_i,Z_i
Xi,Yi,Zi,表示有一条长度为
Z
i
Z_i
Zi 的无向边连接结点
X
i
,
Y
i
X_i,Y_i
Xi,Yi。
如果该图连通,则输出一个整数表示最小生成树的各边的长度之和。如果该图不连通则输出 orz
。
(1)kruskal算法
#include<bits/stdc++.h>
const int maxn = 1e6 + 1;
inline int read(){
register int x = 0, ch = getchar(), f = 1;
while(!isdigit(ch)){if(ch == '-') f = -1; ch = getchar();}
while(isdigit(ch)) x = x * 10 + ch - '0', ch = getchar();
return x * f;
}
int n, m;
struct node{
int u,v,w;
}e[maxn];
int fa[maxn], cnt, sum, num;
void add(int x, int y, int w){
e[++ cnt].u = x;
e[cnt].v = y;
e[cnt].w = w;
}
bool cmp(node x, node y){
return x.w < y.w;
}
int find(int x){
return fa[x] == x ? fa[x] : fa[x] = find(fa[x]);//路径压缩
}
void kruskal(){
for(int i = 1; i <= cnt; i ++){
int x = find(e[i].u);
int y = find(e[i].v);
if(x == y) continue;
fa[x] = y;
sum += e[i].w;
if(++ num == n - 1) break;//如果构成了一颗树
}
}
int main(){
n = read();
m = read();
for(int i = 1; i <= n; i ++) fa[i] = i;
while(m --){
int x, y, w;
x = read();
y = read();
w = read();
add(x, y, w);
}
std:: sort(e + 1, e + 1 + cnt, cmp);
kruskal();
printf("%d",sum);
return 0;
}
(2)prim算法
#include<cstdio>
#include<cstdlib>
#include<iostream>
using namespace std;
int book[100];//用于记录这个点有没有被访问过
int dis[100];//用于记录距离树的距离最短路程
int MAX = 99999;//边界值
int maps[100][100];//用于记录所有边的关系
int i,j,k;//循环变量
int n,m;//输入的N个点,和M条边
int x,y,z;//输入变量
int min,minIndex;
int sum=0;//记录最后的答案
int main(){
cin>>n>>m;
//初始化maps,除了自己到自己是0其他都是边界值
for (i = 1; i <= n; i++){
for (j = 1; j <= n; j++){
if(i!=j) maps[i][j] = MAX;
else maps[i][j] = 0;
}
}
for (i = 1; i <= m; i++){
cin>>x>>y>>z;//输入的为无向图
maps[x][y] = z;
maps[y][x] = z;
}
//初始化距离数组,默认先把离1点最近的找出来放好
for (i = 1; i <= n; i++)
dis[i] = maps[1][i];
book[1]=1;//记录1已经被访问过了
for (i = 1; i <= n-1; i++){//1已经访问过了,所以循环n-1次
min = MAX;//对于最小值赋值,其实这里也应该对minIndex进行赋值,但是我们承认这个图一定有最小生成树而且不存在两条相同的边
//寻找离树最近的点
for (j = 1; j <= n; j++){
if(book[j] ==0 && dis[j] < min){
min = dis[j];
minIndex = j;
}
}
//记录这个点已经被访问过了
book[minIndex] = 1;
sum += dis[minIndex];
for (j = 1; j <= n; j++){
//如果这点没有被访问过,而且这个点到任意一点的距离比现在到树的距离近那么更新
if(book[j] == 0 && maps[minIndex][j] < dis[j])
dis[j] = maps[minIndex][j];
}
}
cout<<sum<<endl;
return 0;
}
(3)链式前项星存图
#include<bits/stdc++.h>
using namespace std;
#define re register
#define il inline
il int read()
{
re int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*f;
}//快读,不理解的同学用cin代替即可
#define inf 123456789
#define maxn 5005
#define maxm 200005
struct edge{
int v,w,next;
}e[maxm<<1];
//注意是无向图,开两倍数组
int head[maxn],dis[maxn],cnt,n,m,tot,now=1,ans;
//已经加入最小生成树的的点到没有加入的点的最短距离,比如说1和2号节点已经加入了最小生成树,那么dis[3]就等于min(1->3,2->3)
bool vis[maxn];
//链式前向星加边
il void add(int u,int v,int w){
e[++cnt].v=v;
e[cnt].w=w;
e[cnt].next=head[u];
head[u]=cnt;
}
//读入数据
il void init(){
n=read(),m=read();
for(re int i=1,u,v,w;i<=m;++i){
u=read(),v=read(),w=read();
add(u,v,w),add(v,u,w);
}
}
il int prim(){
//先把dis数组附为极大值
for(re int i=2;i<=n;++i){
dis[i]=inf;
}
//这里要注意重边,所以要用到min
for(re int i=head[1];i;i=e[i].next){
dis[e[i].v]=min(dis[e[i].v],e[i].w);
}
while(++tot<n){//最小生成树边数等于点数-1
re int minn=inf;//把minn置为极大值
vis[now]=1;//标记点已经走过
//枚举每一个没有使用的点
//找出最小值作为新边
//注意这里不是枚举now点的所有连边,而是1~n
for(re int i=1;i<=n;++i){
if(!vis[i]&&minn>dis[i]){
minn=dis[i];
now=i;
}
}
ans+=minn;
//枚举now的所有连边,更新dis数组
for(re int i=head[now];i;i=e[i].next){
re int v=e[i].v;
if(dis[v]>e[i].w&&!vis[v]){
dis[v]=e[i].w;
}
}
}
return ans;
}
int main(){
init();
printf("%d",prim());
return 0;
}
(4)优先队列+堆优化
#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
#define R register int
using namespace std;
int k,n,m,cnt,sum,ai,bi,ci,head[5005],dis[5005],vis[5005];
struct Edge{
int v,w,next;
}e[400005];
void add(int u,int v,int w){
e[++k].v=v;
e[k].w=w;
e[k].next=head[u];
head[u]=k;
}
typedef pair <int,int> pii;
priority_queue <pii,vector<pii>,greater<pii> > q;
void prim(){
dis[1]=0;
q.push(make_pair(0,1));
while(!q.empty()&&cnt<n){
int d=q.top().first,u=q.top().second;
q.pop();
if(vis[u]) continue;
cnt++;
sum+=d;
vis[u]=1;
for(R i=head[u];i!=-1;i=e[i].next)
if(e[i].w<dis[e[i].v])
dis[e[i].v]=e[i].w,q.push(make_pair(dis[e[i].v],e[i].v));
}
}
int main(){
memset(dis,127,sizeof(dis));
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);
for(R i=1;i<=m;i++){
scanf("%d%d%d",&ai,&bi,&ci);
add(ai,bi,ci);
add(bi,ai,ci);
}
prim();
if (cnt==n)printf("%d",sum);
else printf("orz");
}