算法梳理 (CSP 2019
" 快要考CSP了 "
今年CSP没进TG(四川省喝了假酒
大概只能再骗一次普及了
整理一下算法模板吧
首先是较为简单的一些数论模板
EXGCD
#include<iostream>
using namespace std;
int X,Y;
int x1,y1;
int gcd(int a,int b)
{
return b ? gcd(b,a%b) : a;
}
int exgcd(int a,int b,int &x1,int &y1) //附带求gcd的功能
{
if(b==0){
x=1; //当 ax+by=gcd(a,b)中b=0时,a=gcd(a,b),x=1,y=0
y=0;
return a; //gcd功能实现,b=0时gcd(a,b)=a
}
int x2,y2;
int t=exgcd(b,a%b,x2,y2);
x1=y2;
y1=x2-(a/b)*y2;
return t; //返回gcd(a,b)
}
int main()
{
int a,b,c;
cin>>a>>b>>c;
int l=c/exgcd(a,b,x1,y1);
X=x1*l;
Y=y1*l;
cout<<X<<" "<<Y<<endl;
}
埃氏筛
void work_prime(int n)
{
for(int i=4;i<=n;i+=2){
f[i]=true;
}
for(int i=3;i<=n;i++){
if(!f[i]){
for(int j=i*2;j<=n;j+=i){
f[j]=true;
}
}
}
}
不必过多赘述
接下来是一些基础图论算法的模板
二分图与匈牙利
#include<bits/stdc++.h>
using namespace std;
int ans=0;
const int maxn=1007;
bool vis[maxn];
int n,m;
int e;
bool g[maxn][maxn];
int match[maxn];
bool ap(int x){
for(int j=1;j<=m;++j){//扫描每一个点
if(g[x][j]&&!vis[j]){//如果x,j之间存在一条边且j未被访问过
vis[j]=true; //将j点标记为过
if(!match[j]||ap(match[j])){ //如果这是增广路的中点或还能从这个点找到增广路
match[j]=x; //变反
return true;//返回能找到
}
}
}
return false; //如果扫描了每一个点都无法找到增广路就说明找不到增广路了
}
void work()
{
for(int i=1;i<=n;++i){ //枚举增广路起始点
memset(vis,0,sizeof(vis));
if(ap(i)){
ans++;
}
}
return ;
}
int main()
{
cin>>n>>m>>e;
for(int i=1;i<=e;i++){
int u,v;
cin>>u>>v;
g[u][v]=true;
}
work();
cout<<ans;
}
SPFA
#include <iostream>
#include <stdio.h>
#include <queue>
#include <vector>
#include <math.h>
using namespace std;
const int inf = 2147483647;
const int maxn = 10007;
const int maxm = 500007;
int dist[maxn],inQ[maxn];
vector<int> G[maxn]; //G[i][j] 代表以i点为起始点指向另一个点,的第j条边的编号
queue<int> Q; //记录点的队列,用于一遍遍扫描整张图
int m,n,S;
int U[maxm],V[maxm],C[maxm]; //编号为i的边的左端点,右端点,权值
void spfa(int s) {
for(int i=1;i<=m+1;++i) {
dist[i]=inf;
}
dist[s] = 0;
Q.push(s);
while(!Q.empty()) {
int u=Q.front();Q.pop(); //查找队头的点并将其取出
// cout<<"u:"<<u<<" ";
inQ[u]=0; //将其状态赋为不在队列中
for(int i=0;i<G[u].size();++i) {
int e=G[u][i];
//cout<<"e:"<<e<<" ";
int v=V[e]^U[e]^u; //其实可以直接用V[e]但这里以防万一(吃饱了撑着)还是这样打
if(dist[u]+C[e]<dist[v]) {
dist[v]=dist[u]+C[e];
if(!inQ[v]){
Q.push(v);
inQ[v]=1;
}
}
}
//cout<<endl;
}
return ;
}
int main() {
cin>>m>>n>>S;
for(int i=1;i<=n;i++){
int u,v,c;
cin>>u>>v>>c;
G[u].push_back(i);
U[i]=u;
V[i]=v;
C[i]=c;
}
spfa(S);
for(int i=1;i<=m;i++){
cout<<dist[i]<<" ";
}
return 0;
}
kruskal
#include<bits/stdc++.h>
const int MAX=1000007;
using namespace std;
int m,n;
long long ans=0;
struct edge
{
int val;
int l,r;
} e[MAX];
bool com(edge a,edge b)
{
return a.val<b.val;
}
int fa[5001];
void init(int x)
{
for(;x>=1;x--)
fa[x]=x;
}
int find(int x)
{
if(x==fa[x]) return x;
int k=find(fa[x]);
fa[x]=k;
return k;
}
bool merge(int x,int y)
{
int xx=find(x);
int yy=find(y);
if(xx==yy) return false;
fa[xx]=yy;
return true;
}
int main()
{
cin>>n>>m;
init(n);
for(int i=1;i<=m;++i){
cin>>e[i].l>>e[i].r>>e[i].val;
}
sort(e+1,e+1+m,com);
for(int i=1;i<=m;i++){
if(merge(e[i].l,e[i].r)){
ans+=e[i].val;
}
}
for(int i=2;i<=n;i++){
if(find(i)!=find(i-1)){
cout<<"orz";
return 0;
}
}
cout<<ans;
}
dinic与网络最大流
#include<bits/stdc++.h>
using namespace std;
int INF=1e9;
const int maxn=100007;
const int maxm=100007;
int l[maxn]; //记录当前点在增广路中的深度为深搜提供便利
int head[maxn]; //head[u]记录u点的头指针
struct edge
{
int to; //指向的下一个点的编号
int cost; //边权
int next; //指向的同一起始点的下一条边的编号 方便广搜遍历
} es[maxm*2]; //记录边以及它的反弧
int cnt=0; //记录边的个数即其编号
void init(){
for(int i=1;i<=maxn*2;i++) head[i]=-1;
}
void addedge(int u,int v,int cost) //添加边以及其反弧
{
es[cnt].to=v;
es[cnt].cost=cost;
es[cnt].next=head[u];//添加边
/*
1.将新边的头指针指向原来U点的头指针
2.将原来U点的头指针指向新边
3.并将新边的边权赋为cost
这三步操作即将此边放在链表的首位
*/
head[u]=cnt;
++cnt;
es[cnt].to=u;
es[cnt].cost=0;
es[cnt].next=head[v]; //添加反弧
/*
进行与正边相反的操作
并将反弧的初始权值赋为0
*/
head[v]=cnt;
++cnt;
}
bool ap(int s,int t) //搜索是否能找到增广路
{
memset(l,0,sizeof(l));
l[s]=1;
queue<int> q; //利用队列存点更加便捷
q.push(s);
while(!q.empty()) //广搜过程不需解释
{
int u=q.front();
q.pop();
if(u==t) //如果搜索到了汇点
return true;
for(int v=head[u];v!=-1;v=es[v].next){
int k=es[v].to; //记录当前遍历到的下一个点
if(!l[k]&&es[v].cost){ //如果此点未被遍历且连接两个点的两条边权不为零
l[k]=l[u]+1;
q.push(k);
}
}
}
return false; //没有找到
}
int dfs(int x,int t,int ans) //x为当前遍历到的点,t为汇点,ans为增广路的增益也是流量的限制
{
if(x==t){
return ans;
}
int sum=0;
for(int i=head[x];i!=-1;i=es[i].next){
if(es[i].cost&&l[x]==l[es[i].to]-1){ //如果改边流量没有用完且下一个点在增广路上的下一个点
int f=dfs(es[i].to,t,min(es[i].cost,ans-sum));
es[i].cost-=f;
es[i^1].cost+=f; //反弧加上相应值
sum+=f;
if(sum==ans)
return sum;
}
}
return sum;
}
int dinic(int s,int t) //s为源点,t为汇点
{
int ans=0;
while(ap(s,t)) //重复找s到t的增广路
{
ans+=dfs(s,t,INF);
}
return ans;
}
int N,M,S,T;
int main()
{
init();
cin>>N>>M>>S>>T;
for(int i=1;i<=M;i++){
int aa,bb,cc;
cin>>aa>>bb>>cc;
addedge(aa,bb,cc);
}
int ans=dinic(S,T);
cout<<ans;
return 0;
}
最后是字符串算法
KMP
#include<iostream>
using namespace std;
const int maxn=1000007;
char st[maxn]; //定义模式串
char s[maxn];
int fail[maxn];
int siz=1;
void init()//初始化fail数组
{
fail[0]=0; //根据定义,fail[0]=0
for(int i=1,j=0;st[i];i++){ //i是迭代变量,j存储fail[i-1]
while(j&&st[i]!=st[j]){ //搜索合适的Border
j=fail[j-1];
}
if(st[i]==st[j]){ //找到Border(i)
fail[i]=++j;
}
siz++;
}
}
int search(char *str) //KMP算法的主程序(搜索匹配)部分,str为主串
{
int ans=0;
for(int i=0,j=0;str[i];i++){ //i为主串指针,j为模式串指针
while(j&&str[i]!=st[j]) //搜索合适的Border
j=fail[j-1];
if(str[i]==st[j]){
if(!st[++j]){
ans++;
}
}
}
return ans;
}
int main()
{
cin>>s;
cin>>st;
init();
int ans=search(s); //search函数的返回值为出现的次数
cout<<ans<<endl;
}