存储:邻接表和邻接矩阵
邻接表
const int N = 1e5+10;
int h[N],e[N*2],ne[N*2],idx;
void add(int a,int b)
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
//初始化
idx=0;
memset(h,-1,sizeof h);
[USACO06DEC] Cow Picnic S - 洛谷
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
const int N = 1010;
const int M=1e5+10;
bool st[N];
int cnt[N];
int cow[N];
int k,n,m,ans;
int h[N],e[M],ne[M],idx;
void add(int a,int b)
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u)
{
st[u]=true;
cnt[u]++;
for(int i=h[u];i!=-1;i=ne[i])
{
int j=e[i];
if(st[j]==0)dfs(j);
}
}
int main()
{
cin>>k>>n>>m;
memset(h,-1,sizeof h);
for(int i=1;i<=k;i++)cin>>cow[i];
for(int i=1;i<=m;i++)
{
int a,b;cin>>a>>b;
add(a,b);
}
for(int i=1;i<=k;i++){
memset(st,0,sizeof st);
dfs(cow[i]);//每一头奶牛来遍历一下
}
for(int i=1;i<=n;i++)
{
if(cnt[i]==k)ans++;
}
cout<<ans<<endl;
return 0;
}
树和图的深度优先搜索的代码框架
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5+10;
int h[N],e[N*2],ne[N*2],idx;
void add(int a,int b)
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u)
{
st[u]=true;
for(int i=h[u];i!=-1;i=ne[i])
{
int j=e[i];
if(st[j]==0)dfs(j);
}
}
int main()
{
memset(h,-1,sizeof h);
dfs(1);
}
邻接矩阵
g [ a ][ b ]存储a->b的距离
树的DFS例题
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5+10;
int h[N],e[N*2],ne[N*2],idx;
int n;
int ans=N;
bool st[N*2];
void add(int a,int b)
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int dfs(int u)
{
int res=0;
st[u]=true;
int sum=1;//存储以u为根的树的节点数,包括u
for(int i=h[u];i!=-1;i=ne[i])
{
int j=e[i];
if(st[j]==0)
{
int s=dfs(j);// u节点的单棵子树节点数
res=max(res,s);
sum+=s;
}
}
res=max(res,n-sum);
ans=min(res,ans);
return sum;
}
int main()
{
memset(h,-1,sizeof h);
cin>>n;
for(int i=0;i<n-1;i++)
{
int a,b;
cin>>a>>b;
add(a, b);
add(b, a);
}
dfs(1);
cout<<ans<<endl;
return 0;
}
拓扑排序(BFS的应用) 大致两步
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 1e5+10;
int h[N], e[N], ne[N], idx;
int n,m;
int d[N];
int ans[N],cnt;
void add(int a,int b)
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int topp()
{
queue<int>q;
for(int i=1;i<=n;i++)
{
if(d[i]==0)q.push(i);
}
while(!q.empty())
{
int t=q.front();q.pop();
ans[cnt++]=t;
for(int i=h[t];i!=-1;i=ne[i])
{
int j=e[i];
d[j]--;//减边的操作
if(d[j]==0)
{
q.push(j);
}
}
}
return cnt==n;
}
int main()
{
memset(h, -1, sizeof h);
cin>>n>>m;
while (m -- )
{
int a,b;
cin>>a>>b;
add(a, b);
d[b]++;
}
if(topp())
{
for(int i=0;i<cnt;i++)
{
cout<<ans[i]<<" ";
}
}
else cout<<"-1"<<endl;
return 0;
}
拓扑排序的基础上求最大距离
杂务 - 洛谷 套模板套的好爽,前提是得识别出来这是个拓扑排序
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N=1e5+10;
int h[N],e[N],ne[N],idx;
int d[N],Time[N],f[N];
int n,m,ans=-1;
queue<int>q;
void add(int a,int b)
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void topsort()
{
//遍历入度+入队
for(int i=1;i<=n;i++)
{
if(d[i]==0)
{
q.push(i);
f[i]=Time[i];//初始化距离
}
}
//循环处理队列中的那些点
while(!q.empty())
{
int t=q.front();q.pop();
//邻
for(int i=h[t];i!=-1;i=ne[i])
{
int j=e[i];
--d[j];
if(d[j]==0)
{
q.push(j);
}
f[j]=max(f[j],f[t]+Time[j]);//主要是这一步
}
}
}
int main()
{
cin>>n;
memset(h,-1,sizeof h);
for(int i=0;i<n;i++)
{
int a,b;
cin>>a>>b;
Time[a]=b;//需要的时间
int x;
while(cin>>x)
{
if(x==0)break;
add(x,a);
d[a]++;//初始化这个入度
}
}
topsort();
for(int i=1;i<=n;i++)
{
ans=max(ans,f[i]);
}
cout<<ans<<endl;
return 0;
}
食物链 拓扑排序加了一个num数组
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 5010, M = 500010;
int h[N], e[M], ne[M], idx;
int d[N], out[N],Time[N], f[N];
int n, m, ans = 0;
int num[N]; // 存放能走到该节点的方案数
queue<int> q;
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void topsort()
{
// 遍历入度+入队
for (int i = 1; i <= n; i++)
{
if (d[i] == 0)
{
num[i]=1;
q.push(i);
}
}
// 循环处理队列中的那些点
while (!q.empty())
{
int t = q.front();
q.pop();
// 邻
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
--d[j];
num[j] = (num[j] + num[t]) % 80112002;
if (d[j] == 0)
{
q.push(j);
}
}
}
}
int main()
{
cin >>n>>m;
memset(h, -1, sizeof h);
for (int i = 0; i < m; i++)
{
int a, b;
cin >> a >> b;
add(a,b);
d[b]++; // 初始化这个入度
out[a]++;
}
topsort();
for (int i = 1; i <= n; i++) // 找出出度为0的点
if (!out[i])
ans = (ans + num[i]) % 80112002;
cout << ans << endl;
return 0;
}
最短路
dijkstra算法
朴素版的
朴素版+结点存在权值
L2-001 紧急救援 - 团体程序设计天梯赛-练习集 (pintia.cn)
#include<bits/stdc++.h>
using namespace std;
const int N = 510;
int n,m,s,d;
int city[N];//救援队的数量
int g[N][N];
int dist[N];
int path[N];
int cnt[N],group[N];
bool st[N];
void dijkstra()
{
memset(dist,0x3f,sizeof dist);
dist[s]=0;
group[s]=city[s];cnt[s]=1;
for(int i=0;i<n;i++)
{
int t=-1;
for(int j=0;j<n;j++)
{
if(!st[j]&&(t==-1||dist[j]<dist[t]))t=j;
}
st[t]=true;
for(int j=0;j<n;j++)
{
if(dist[j]>dist[t]+g[t][j]){
dist[j]=dist[t]+g[t][j];
path[j]=t;//记录
cnt[j]=cnt[t];
group[j]=group[t]+city[j];
}
else if(dist[j]==dist[t]+g[t][j]){
dist[j]=dist[t]+g[t][j];
cnt[j]+=cnt[t];
if(group[j]<group[t]+city[j])
{
group[j]=group[t]+city[j];
path[j]=t;
}
}
}
}
}
void printPath(int u)
{
if(u==s){
cout<<u;
return ;
}
printPath(path[u]);
cout<<" "<<u;
return;
}
int main()
{
cin>>n>>m>>s>>d;
memset(g,0x3f,sizeof g);
for(int i=0;i<n;i++)cin>>city[i];
while(m--)
{
int a,b,c;cin>>a>>b>>c;
g[a][b]=g[b][a]=min(g[a][b],c);
}
dijkstra();
cout<<cnt[d]<<" "<<group[d]<<endl;//最优的路径条数和救援队的总数
//打印路径
printPath(d);
return 0;
}
朴素版的变形题
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
const int M = 1e5+10;
int g[N][N];
int dist[N];
bool st[N];
int q[N];
// int e[M],ne[M],h[N],idx,w[M];
int n,m;
bool dijkstra()
{
memset(dist,0x3f,sizeof dist);
memset(st,0,sizeof st);
dist[q[0]]=0;
for(int i=0;i<n;i++)
{
int t=q[i];//遍历这个序列
for(int j=1;j<=n;j++)if(st[j]==0&&dist[j]<dist[t])return false;
st[t]=true;
for(int j=1;j<=n;j++)dist[j]=min(dist[j],dist[t]+g[t][j]);
}
return true;
}
int main()
{
cin>>n>>m;
memset(g,0x3f,sizeof g);
while (m -- ){
int a,b,w;
cin>>a>>b>>w;
g[a][b]=g[b][a]=w;
}
int k;cin>>k;
while(k--)
{
for(int i=0;i<n;i++)cin>>q[i];
if(dijkstra())cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
return 0;
}
堆优化版的
#include <iostream>
#include <cstring>
#include <algorithm>
#include<queue>
using namespace std;
const int N = 1e6 + 10;
typedef pair<int, int> PII;
int h[N], e[N], ne[N],w[N], idx;
bool st[N];
int dist[N];
int n,m,s;
void add(int a, int b, int c) // 添加一条边a->b,边权为c
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
void dijkstra()
{
memset(dist,0x3f,sizeof dist);
dist[s]=0;
priority_queue<PII, vector<PII>, greater<PII> >heap;
heap.push({0,s});//距离+编号
while(heap.size())
{
PII t=heap.top();heap.pop();
int id=t.second,distance=t.first;
if(st[id])continue;
st[id]=true;
for(int i=h[id];i!=-1;i=ne[i])
{
int j=e[i];
if(dist[j]>dist[id]+w[i])
{
dist[j]=dist[id]+w[i];
heap.push({dist[j],j});
}
}
}
}
int main()
{
cin>>n>>m>>s;//n个点m条边
memset(h,-1,sizeof h);
while (m -- ){
int a,b,c;
cin>>a>>b>>c;
add(a, b, c);
}
dijkstra();
for(int i=1;i<=n;i++)
{
cout<<dist[i]<<" ";
}
return 0;
}
无向边的dijkstra只需要在建边的时候建两次就行
add(a, b, c);
add(b, a, c);
[USACO07NOV] Cow Hurdles S - 洛谷
试一下这个题能不能用dijkstra算法(堆优化+有向边(max和min))
SPFA
关于SPFA的经典好题 - 题单 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
模板
#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
const int N=1e5+10;
#define fi first
#define se second
typedef pair<int,int> PII;//到源点的距离,下标号
int h[N],e[N],w[N],ne[N],idx=0;
int dist[N];//各点到源点的距离
bool st[N];
int n,m;
void add(int a,int b,int c){
e[idx]=b;w[idx]=c;ne[idx]=h[a];h[a]=idx++;
}
int spfa(){
memset(dist,0x3f,sizeof dist);
dist[1]=0;
queue<int>q;
q.push(1);
st[1]=true;//已经在队列里面了
while(q.size())
{
int t=q.front();q.pop();
st[t]=false;//出队列了
for(int i=h[t];i!=-1;i=ne[i])
{
int j=e[i];
if(dist[t]+w[i]<dist[j])
{
dist[j]=dist[t]+w[i];
if(st[j]==0)
{
q.push(j);
st[j]=true;//已经在队列里面了
}
}
}
}
if(dist[n]==0x3f3f3f3f)return 0x3f3f3f3f;
else return dist[n];
}
int main(){
scanf("%d%d",&n,&m);
memset(h,-1,sizeof h);
while(m--){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
int res=spfa();
if(res==0x3f3f3f3f) puts("impossible");
else printf("%d",res);
return 0;
}
如果要判断是否存咋负环,可以开一个数组cnt来记录边数,如果边数>=n的话,使用容斥原理可以知道存在负环。
#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
const int N=1e5+10;
#define fi first
#define se second
typedef pair<int,int> PII;//到源点的距离,下标号
int h[N],e[N],w[N],ne[N],idx=0;
int dist[N],cnt[N];//各点到源点的距离,加一个cnt记录所有遍历的边数,然后用容斥原理就可以判断是否有负环了
bool st[N];
int n,m;
void add(int a,int b,int c){
e[idx]=b;w[idx]=c;ne[idx]=h[a];h[a]=idx++;
}
int spfa(){
memset(dist,0x3f,sizeof dist);
dist[1]=0;
queue<int>q;
for(int i=1;i<=n;i++)
{
st[i]=true;
q.push(i);
}
while(q.size())
{
int t=q.front();q.pop();
st[t]=false;//出队列了
for(int i=h[t];i!=-1;i=ne[i])
{
int j=e[i];
if(dist[t]+w[i]<dist[j])
{
dist[j]=dist[t]+w[i];
cnt[j]=cnt[t]+1;
if(cnt[j]>n)return true;
if(st[j]==0)
{
q.push(j);
st[j]=true;
}
}
}
}
return false;
}
int main(){
scanf("%d%d",&n,&m);
memset(h,-1,sizeof h);
while(m--){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
int res=spfa();
if(res==1) puts("Yes");
else printf("No");
return 0;
}
Floyd
Floyd - 题单 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 210, INF = 1e9;
int n, m, Q;
int d[N][N];
void floyd()
{
for (int k = 1; k <= n; k ++ )//k要放在最外层
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);// i j = i k k j
}
int main()
{
scanf("%d%d%d", &n, &m, &Q);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
if (i == j) d[i][j] = 0;
else d[i][j] = INF;
while (m -- )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
d[a][b] = min(d[a][b], c);//有向图
}
floyd();
while (Q -- )
{
int a, b;
scanf("%d%d", &a, &b);
int t = d[a][b];
if (t > INF / 2) puts("impossible");
else printf("%d\n", t);
}
return 0;
}
洛谷的模板题(只是加了一个重边的条件)
#include<bits/stdc++.h>
using namespace std;
const int N=110;
const int M=4510;
// int h[N],e[M],ne[M],w[M],idx;
int d[N][N];
int n,m;
// void add(int a,int b,int c)
// {
// e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
// }
void floyd()
{
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
}
int main()
{
cin>>n>>m;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
{
if(i==j)d[i][j]=0;
else d[i][j]=0x3f3f3f3f;
}
for (int i = 0; i < m; i++)
{
int a, b, c;
cin >> a >> b >> c;
d[a][b] =min(d[a][b],c);
d[b][a] =min(d[b][a],c);
}
floyd();
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
cout<<d[i][j]<<" ";
}
cout<<endl;
}
return 0;
}
P2888 [USACO07NOV] Cow Hurdles S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
这个题,稍微变了一下这个方程:d[i][j]=min(d[i][j],max(d[i][k],d[k][j]));
#include<bits/stdc++.h>
using namespace std;
const int N=310,INF=0x3f3f3f3f;
int n,m,T;
int d[N][N];
void floyd()
{
for(int k=1;k<=n;k++)
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
d[i][j]=min(d[i][j],max(d[i][k],d[k][j]));
}
}
}
}
int main()
{
cin>>n>>m>>T;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(i==j)d[i][j]=0;
else d[i][j]=INF;
while(m--)
{
int a,b,w;
cin>>a>>b>>w;
d[a][b]=min(d[a][b],w);
}
floyd();
while(T--)
{
int a,b;
cin>>a>>b;
int dis=d[a][b];
if(dis>INF/2)cout<<"-1"<<endl;
else cout<<dis<<endl;
}
return 0;
}
然后我发现23年蓝桥杯有一道题可以用Floyd的这个例题相同的思路来骗分(无向图,建边的时候建两次就行)
看到题解里面说的:P3379 最近公共祖先【黄】+P3366 最小生成树【橙】=P9235 网络稳定性【蓝】,但是我还没学最近公共祖先
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 510, INF = 1e9;
int n, m, Q;
int d[N][N];
void floyd()
{
for (int k = 1; k <= n; k ++ )//k要放在最外层
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
d[i][j] = max(d[i][j], min(d[i][k],d[k][j]));// i j = i k k j
}
int main()
{
scanf("%d%d%d", &n, &m, &Q);
//初始化
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
d[i][j] = -1;
while (m -- )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
d[a][b] = max(d[a][b], c);
d[b][a] = max(d[b][a], c);//无向
}
floyd();
while (Q -- )
{
int a, b;
scanf("%d%d", &a, &b);
int t = d[a][b];
printf("%d\n", t);
}
return 0;
}
最小生成树
prim朴素版算法的模版
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 510,INF=0x3f3f3f3f;
int n,m;
int g[N][N];
int dist[N];
bool st[N];
int prim()
{
memset(dist,0x3f,sizeof dist);
int res=0;
for(int i=0;i<n;i++)
{
int t=-1;
for(int j=1;j<=n;j++)
{
//没有进入集合+距离比较小,更新
if(!st[j]&&(t==-1||dist[j]<dist[t]))t=j;
}
//如果不连通,这个需要记一下,
if(i&&dist[t]==INF)return INF;
if(i)res+=dist[t];
st[t]=true;
for(int j=1;j<=n;j++)
{
dist[j]=min(dist[j],g[t][j]);//和最短路的区别就只是在这里
//求解到集合的最短距离
}
}
return res;
}
int main()
{
scanf("%d%d", &n, &m);
memset(g, 0x3f, sizeof g);
while (m -- ){
int a,b,c;
scanf("%d%d%d", &a, &b,&c);
g[a][b]=g[b][a]=min(g[a][b],c);
}
int t = prim();
if (t == INF) puts("impossible");
else printf("%d\n", t);
return 0;
}
Kruskal算法
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100010, M = 200010, INF = 0x3f3f3f3f;
int n, m;
int p[N];
struct Edge
{
int a,b,w;
bool operator <(const Edge &W)const
{
return w<W.w;
}
}edges[M];
int find(int x)
{
if(p[x]!=x)p[x]=find(p[x]);
return p[x];
}
int kruskal()
{
sort(edges,edges+m);
for(int i=1;i<=n;i++)p[i]=i;
int res=0;//记录权重之和
int cnt=0;//记录边数
for(int i=0;i<m;i++)
{
int a=edges[i].a,b=edges[i].b,w=edges[i].w;
a=find(a),b=find(b);
if(a!=b)//使用并查集进行查询
{
p[a]=b;
res+=w;
cnt++;
}
}
if(cnt<n-1)return INF;
else return res;
}
// int kruskal()
// {
// sort(edges,edges+m);
// for(int i=1;i<=n;i++)p[i]=i;
// int res=0,cnt=0;
// for(int i=0;i<m;i++)
// {
// int a=edges[i].a,b=edges[i].b,w=edges[i].w;
// a=find(a),b=find(b);
// if(a!=b)
// {
// p[a]=b;//合并
// res+=w;
// cnt++;
// }
// }
// if(cnt<n-1)return INF;
// return res;
// }
int main()
{
scanf("%d%d", &n, &m);
for (int i = 0; i < m; i ++ )
{
int a, b, w;
scanf("%d%d%d", &a, &b, &w);
edges[i] = {a, b, w};
}
int t = kruskal();
if (t == INF) puts("impossible");
else printf("%d\n", t);
return 0;
}