排列数字(DFS)
使用DFS进行排列(回溯),递归
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| #include<iostream> using namespace std; const int N=10; int path[N],n; bool st[N]; void dfs(int u){ if(u==n){ for(int i=0;i<n;i++){ printf("%d ",path[i]); } puts(""); return; } for(int i=1;i<=n;i++){ if(!st[i]){ path[u]=i; st[i]=true; dfs(u+1); st[i]=false; } } } int main(){ cin>>n; dfs(0); return 0; }
|
走迷宫(BFS)
优势:搜到最短路
最短路<–>Dp问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| #include<cstring> #include<iostream> #include<algorithm> #include<queue> using namespace std; typedef pair<int,int> PII; int n,m; const int N=110; int g[N][N],d[N][N]; PII q[N*N]; int bfs(){ int hh=0,tt=0; memset(d,-1,sizeof(d)); d[0][0]=0; q[0]={0,0}; int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1}; while(hh<=tt){ auto t=q[hh++]; for(int i=0;i<4;i++){ int x=t.first+dx[i],y=t.second+dy[i]; if(x>=0&&x<n&&y>=0&&y<m&&g[x][y]==0&&d[x][y]==-1){ d[x][y]=1+d[t.first][t.second]; q[++tt]={x,y}; } } } return d[n-1][m-1]; } int main(){ cin>>n>>m; for(int i=0;i<n;i++){ for(int j=0;j<m;j++){ cin>>g[i][j]; } } cout<<bfs()<<endl; return 0; }
|
图的遍历
DFS:一条路走到死
BFS:所有距离的点依次搜索出来
1 2 3 4 5 6 7 8
| int dfs(int u){ st[u]=true; int size=0,sum=0; for(int i=h[u];i!=-1;i=ne[i]){ int j=e[i]; if(!st[j]) dfs(j); } }
|
题目:
1 2 3 4 5
| 给定一颗树,树中包含 n 个结点(编号 1∼n)和 n−1 条无向边。
请你找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。
重心定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| #include<iostream> #include<cstring> #include<algorithm> #include<cstdio> using namespace std; const int N=100010,M=N*2; int n; int h[N],e[M],ne[M],idx; int ans=N; bool st[N]; void add(int a,int b){ e[idx]=b; ne[idx]=h[a]; h[a]=idx++; } int dfs(int u){ st[u]=true; int size=0,sum=0; for(int i=h[u];i!=-1;i=ne[i]){ int j=e[i]; if(st[j]) continue; int s=dfs(j); size=max(size,s); sum+=s; } size=max(size,n-sum-1); ans=min(size,ans); return sum+1; } int main() { memset(h,-1,sizeof(h)); scanf("%d",&n); for(int i=0;i<n-1;i++){ int a,b; scanf("%d%d",&a,&b); add(a,b),add(b,a); } dfs(1); printf("%d\n",ans); return 0; }
|
BFS(所有长度都是1)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| #include<iostream> #include<cstring> #include<queue> #include<algorithm> #include<cstdio> using namespace std; const int N=100010; int h[N],e[N],ne[N],idx; int d[N],q[N]; int st[N]; int n,m; void add(int a,int b){ e[idx]=b,ne[idx]=h[a],h[a]=idx++; } int bfs(){ int hh=0,tt=0; q[0]=1; memset(d,-1,sizeof d); d[1]=0; while(hh<=tt){ int t=q[hh++]; for(int i=h[t];i!=0;i=ne[i]){ int j=e[i]; if(d[j]==-1){ d[j]=d[t]+1; q[++tt]=j; } } } return d[n]; } int main(){ cin>>n>>m; memset(h,-1,sizeof h); for(int i=0;i<m;i++){ int a,b; scanf("%d%d",&a,&b); add(a,b); } cout<<bfs()<<endl; return 0; }
|
有向图的拓扑排序(BFS应用)
注意:有向图才有拓扑序列
拓扑序列:对于同样的起点和终点,不管怎么走,起点永远在终点的前面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| #include<iostream> #include<cstring> #include<algorithm> using namespace std; const int N=100010; int q[N],d[N],h[N],e[N],ne[N],idx; int n,m,tt=-1,hh=0; 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[++tt]=i; } while(hh<=tt){ int a=q[hh++]; for(int i=h[a];i!=-1;i=ne[i]){ int b=e[i]; d[b]--; if(d[b]==0){ q[++tt]=b; } } } if(tt==n-1){ for(int i=0;i<n;i++){ printf("%d ",q[i]); } } else cout<<"-1"; } int main(){ cin>>n>>m; memset(h,-1,sizeof h); while(m--){ int a,b; cin>>a>>b; add(a,b); d[b]++; } topsort(); return 0; }
|
Dijkstra
朴素版的Dijkstra(有向图)
初始化:dist[1]=0,dist[i]=+∞(用一个很大的数代替即可)
s:当前已经确定了最短距离的点
for(i=0;i<n;i++){//每次找到距离最小的点,然后用这个点更新剩下点到
t<-不在s中的距离最近的点
s<-t
(if(dist[x]>dist[t]+w)//用t更新其他所有点的距离
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| 3. 求时间复杂度:O(n^2^)
4. 稠密图:用邻接矩阵存储 稀疏图:用邻接表存储
---
```C++ #include<iostream>//dijkstra算法不支持负权边 #include<cstring> #include<algorithm> using namespace std; const int N=520; int m,n; int dist[N],g[N][N];//距离,地图 bool st[N];//每个点是否已确定 int dijkstra(){ memset(dist,0x3f,sizeof dist);//初始化距离为+∞ dist[1]=0; for(int i=1;i<=n;i++){ int t=-1; for(int j=1;j<=n;j++){ if(!st[j]&&(t==-1||dist[j]<dist[t])) t=j; }//找到所有没确定的点中的距离的最小值 st[t]=true; for(int j=1;j<=n;j++){ dist[j]=min(dist[j],dist[t]+g[t][j]); }//用t更新距离 } if(dist[n]==0x3f3f3f3f) return -1;//1、n不连通 else return dist[n]; } 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);//读入m条边 g[a][b]=min(g[a][b],c);//重边只保留长度最短的 } int t=dijkstra(); printf("%d",t); return 0; }
|
堆优化版的dijkstra算法
在每次寻找没被纳入的点中距离最短的点的时候,使用堆优化,把这一步的时间复杂度从m变成1
手写堆:可以实现修改任意值(映射)
优先队列:不支持任意修改元素 O(mlogn)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| #include<iostream> #include<queue> #include<cstring> #include<algorithm> using namespace std; typedef pair<int,int> PII; const int N=150010; int n,m; int dist[N],e[N],w[N],ne[N],idx,h[N]; bool st[N]; void add(int a,int b,int c){ e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++; } int dijkstra(){ memset(dist,0x3f,sizeof dist); dist[1]=0; priority_queue <PII,vector<PII>,greater<PII>> heap; heap.push({0,1}); while(heap.size()){ auto t=heap.top(); heap.pop(); int ver=t.second; if(st[ver]) continue; st[ver]=true; for(int i=h[ver];i!=-1;i=ne[i]){ int j=e[i]; if(dist[j]>dist[ver]+w[i]){ dist[j]=dist[ver]+w[i]; heap.push({dist[j],j}); } } } if(dist[n]==0x3f3f3f3f) return -1; 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 t=dijkstra(); cout<<t<<endl; return 0; }
|
Bellman-Ford算法
1 2 3 4 5
| for n次迭代 for 所有边 备份,防止边数限制 for 所有边a,b,w dist[b]=min(dist[b],dist[a]+w)//循环完之后,对于多有的边,一定满足dist[b]≤dist[a]+w(三角不等式),松弛操作,不能有负权回路->不超过k条边
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| #include<iostream> #include<cstring> #include<algorithm> using namespace std; const int N=510; int dist[N],last[N],m,n,k; struct{ int a,b,w; }edges[10010]; void bellman_ford(){ memset(dist,0x3f,sizeof dist); dist[1]=0; for(int i=0;i<k;i++){ memcpy(last,dist,sizeof dist); for(int j=0;j<m;j++){ auto e=edges[j]; dist[e.b]=min(last[e.a]+e.w,dist[e.b]); } } if(dist[n]>0x3f3f3f3f/2) puts("impossible");防止出现0x3f3f3f3f-1这种 else cout<<dist[n]; } int main(){ cin>>n>>m>>k; for(int i=0;i<m;i++){ cin>>edges[i].a>>edges[i].b>>edges[i].w; } bellman_ford(); return 0; }
|
spfa算法
求最短路
要求不含负环
1 2 3 4 5
| for n次迭代 for 所有边 备份,防止边数限制 for 所有边a,b,w dist[b]=min(dist[b],dist[a]+w)//优化:只有dist[a]变化时,才需要更新dist[b]
|
每一次先把起点放到队列,只要队列不空,每一次有边变小时:
t<-q.front
取出队头
q.pop()
- 更新t的所有出边
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| #include<iostream> #include<cstring> #include<queue> #include<algorithm> using namespace std; const int N=100010; int dist[N],m,n,e[N],ne[N],h[N],w[N],idx; bool st[N]; 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[j]>dist[t]+w[i]){ dist[j]=dist[t]+w[i]; if(!st[j]){ st[j]=true; q.push(j); } } } } return dist[n]; } int main(){ cin>>n>>m; memset(h,-1,sizeof h); while(m--){ int a,b,c; cin>>a>>b>>c; add(a,b,c); } int t=spfa(); if(t==0x3f3f3f3f) puts("impossible"); else printf("%d",dist[n]); return 0; }
|
Floyd算法
求多元最短路
d[i,j]:存储所有边
1 2 3 4 5 6 7
| for(k=1;k<=n;k++){ for(int i=1;i<=n;i++){ for(intj=1;j<=n;j++){ d[i,j]=min(d[i,j],d[i,k]+d[k,j]) } } }
|
不存在负权回路(最短距离会变成-∞)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| #include<iostream> #include<cstring> #include<algorithm> using namespace std; const int N=210; int INF=1e9,m,n,k; 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],d[i][k]+d[k][j]); } } } } int main(){ scanf("%d%d%d",&n,&m,&k); 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 x,y,z; cin>>x>>y>>z; d[x][y]=min(d[x][y],z); } floyd(); while(k--){ int a,b; cin>>a>>b; if(d[a][b]>INF/2) cout<<"impossible"<<endl; else cout<<d[a][b]<<endl; } return 0; }
|
Prim算法
朴素版
1 2 3 4 5 6
| 初始化所有距离为+∞ for(i=0;i<n;i++){ t<-找到不在集合中的距离最小的点(集合:连通块) 用t更新其他点到集合的距离//dijkstra:更新的是其他点到起点的距离 st[t]=true }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| #include<iostream> #include<algorithm> #include<cstring> using namespace std; const int N=510,INF=0x3f3f3f3f; int n,m,g[N][N],dist[N]; bool st[N]; int prim(){ memset(dist,INF,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[t]>dist[j])){ t=j; } } st[t]=true; if(i&&dist[t]==INF) return INF; if(i) res+=dist[t]; for(int j=1;j<=n;j++){ dist[j]=min(dist[j],g[t][j]); } } return res; } int main(){ cin>>n>>m; for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ if(i==j) g[i][j]=0; else g[i][j]=INF; } } int u,v,w; while(m--){ cin>>u>>v>>w; g[v][u]=g[u][v]=min(g[u][v],w); } int t=prim(); if(t>INF/2) puts("impossible"); else cout<<t<<endl; return 0; }
|
Kruskal算法
1 2 3 4
| 将所有边按照权重从小到大排序(可以用快排)0(mlogm) 枚举每条边a,b,权重为c if(a,b)不连通 将这条边加入集合
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| #include<iostream> #include<algorithm> using namespace std; const int N=200010; int m,n,p[N],res,cnt; struct edge{ int a,b,w; bool operator< (const edge &W) const{ return w<W.w; } }edges[N]; int find(int x){ if(p[x]!=x) p[x]=find(p[x]); return p[x]; } int kruskal(){ for(int i=0;i<m;i++){ int a=edges[i].a,b=edges[i].b,w=edges[i].w; if(find(a)!=find(b)){ p[find(a)]=p[find(b)]; res+=w; cnt++; } } if(cnt==n-1){ return res; } else return 0x3f3f3f3f; } int main(){ cin>>n>>m; for(int i=0;i<n;i++) p[i]=i; for(int i=0;i<m;i++){ cin>>edges[i].a>>edges[i].b>>edges[i].w; } sort(edges,edges+m); int t=kruskal(); if(t==0x3f3f3f3f) puts("impossible"); else cout<<t; return 0; }
|
二分图
染色法
一个图是二分图,当且仅当图中不含奇数环
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| #include<iostream> #include<cstring> #include<algorithm> using namespace std; const int N=100010,M=200010; int h[N],e[M],ne[M],n,m,idx,color[N]; void add(int a,int b){ e[idx]=b,ne[idx]=h[a],h[a]=idx++; } bool dfs(int x,int c){ color[x]=c; for(int i=h[x];i!=-1;i=ne[i]){ int j=e[i]; if(!color[j]){ if(!dfs(j,3-c)){ return false; } } else if(color[j]==c) return false; } return true; } int main(){ cin>>n>>m; memset(h,-1,sizeof h); while(m--){ int a,b; cin>>a>>b; add(a,b),add(b,a); } bool flag=true; for(int i=1;i<=n;i++){ if(!color[i]){ if(!dfs(i,1)){ flag=false; break; } } } if(!flag) puts("No"); else puts("Yes"); return 0; }
|
二分图的最大匹配
匈牙利算法:再比较快的时间内告诉我们左右两边最大的匹配成功数
时间复杂度:O(n*m) 最坏情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| #include<iostream> #include<cstring> using namespace std; const int N=510,M=100010; int h[N],e[M],n1,n2,m,res,ne[M],idx; bool st[N]; int match[N]; void add(int a,int b){ e[idx]=b,ne[idx]=h[a],h[a]=idx++; } bool find(int x){ for(int i=h[x];i!=-1;i=ne[i]){ int j=e[i]; if(!st[j]){ st[j]=true; if(0==match[j]||find(match[j])){ match[j]=x; return true; } } } return false; } int main(){ cin>>n1>>n2>>m; memset(h,-1,sizeof h); while(m--){ int a,b; cin>>a>>b; add(a,b); } for(int i=1;i<=n1;i++){ memset(st,false,sizeof st); if(find(i)) res++; } cout<<res<<endl; return 0; }
|