DFS
连通
P352 连通分量(无向图),强连通分量(有向图)
题目(连通)pata1034 head of a gang
核心代码:
void dfs( int u,int& head,int &numMember,int &totalvalue )//head,numMember,totalvalue随每一次递推dfs改变,但它们最终结果以最后一次递推为准(不具备回溯)
{
vis[u]=true;
if( w[u] > w[head] ) head=u;
numMember++;
for(int i=0;i<index;i++)
{
if( G[u][i]>0 )//能从u点达到i点
{
totalvalue+= G[u][i];//能从u点达到i点就加上u~i边的边权,不要求i点未被访问过
G[u][i]=G[i][u]=0;
if(vis[i]==false) dfs(i,head,numMember,totalvalue);
}
}
}
void dfstrave()
{
for(int i=0;i<index;i++)
{
if(vis[i]==false)
{
int head=i,numMember=0,totalvalue=0;
dfs(i,head,numMember,totalvalue);
if( numMember>2 && totalvalue>k )
{
gang[ int2str[head] ] = numMember;
}
}
}
}
学习点 1:void dfs( int u,int& head,int &numMember,int &totalvalue )
head,numMember,totalvalue随每一次递推dfs改变,但它们最终结果以最后一次递推为准(不具备回溯)
学习点2:题目需要加上连通图的所有边权,4号边的边权也不能漏,所以代码用的是if( G[u][i]>0 ) totalvalue+= G[u][i]
,而不是if( G[u][i]>0 && vis[i]==false ) totalvalue+= G[u][i]
最小生成树
MST性质
Prime算法
U集合 为落在最小生成树中的顶点,TE集合 为最小生成树中的边集合,V-U集合 为尚未落在最小生成树中的顶点集,在U集合和V-U集合之间找一条最小权值的边,然后将该边的另一端顶点并入到U集合中,并将该边加入到TE集合中。
kruskal算法
开始每个顶点自成一个连通分量,在保证不成环的情况下,不断地加入最小权值的边,直到所有点都在同一个连通分量下。
最短路径算法
Dijkstra算法
用于解决单源最短路径问题
基本思想:对图G(V,E)设置集合S,存放已被访问过的顶点(这些顶点已经被求出最短路径),然后每次从集合V-S中选择与起点s的最短路径最小的一个顶点(记为u),访问并加入集合S。之后令u为中介点,优化起点s与所有从u能到到的顶点v之间的最短距离。这样的操作执行n次(n为顶点个数),直到集合S已包含所有顶点。
邻接矩阵版
不要忘了初始化,G数组:inf,dist数组:inf
时间复杂度O(V^2)
注意输入:
fill(G[0],G[0]+maxn*maxn,inf);//别漏
for(int i=0;i<m;i++)
{
int A,B,len;
scanf("%d %d %d",&A,&B,&len);
G[A][B]=min(len,G[A][B]);
G[B][A]=G[A][B];//无向图
}
注意下面写法不要提前加上vis[s]=true,因为第一步就是将s点纳入S集合
int n,G[maxn][maxn];
int d[maxn];
bool vis[maxn]={false};
void dijkstra(int s)//s为起点
{
fill(d,d+maxn,inf);
d[s]=0;
for(int i=0;i<n;i++)//循环n次
{
int u=-1,MIN=inf;
for(int j=0;j<n;j++)//找到V-S集合中距离起点最近的u
{
if(vis[j]==false && d[j]<MIN)
{
u=j;
MIN=d[j];
}
}
if(u==-1) return;//剩下的顶点与起点不连通
vis[u]=true;//将u加入S集合中
for(int v=0;v<n;v++)
{
//以u为中继节点做松弛操作
if(vis[v]==false && G[u][v]!=inf && d[u]+G[u][v]<d[v] )
{
d[v]=d[u]+G[u][v];
}
}
}
}
领接矩阵版改进
有时候题目有要求,比如在最短路径有多条的情况下,我们要求点权最小或者边权最小(边权不一定是距离),我们可以先记录所有的最短路径,再dfs出符合的情况。
因为存在d[u]+G[u][v]==d[v]
,所以前驱可能有多个,使用vector<int> pre[maxn]
存储
vector<int> pre[maxn];
void dijkstra(int s)
{
fill(d,d+maxn,inf);
d[s]=0;
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
int u=-1,MIN=inf;
if( vis[j]==false && d[j]<MIN )
{
u=j;
MIN=d[j];
}
}
if(u==-1) return;
vis[u]=true;
for(int v=0;v<n;v++)
{
if(vis[v]==false && G[u][v]!=inf )
{
if(d[u]+G[u][v] < d[v])
{
d[v]=d[u]+G[u][v];
pre[v].clear();//将v的前驱结点修改为u
pre[v].push_back(u);
}
else if(d[u]+G[u][v] == d[v])
{
pre[v].push_back(u);//有多个前驱结点
}
}
}
}
}
关于后面如何dfs:
从尾结点开始dfs,递归的边界为到达路径的起点,到达边界后算出此时到 value,如果该value优于记录的optvalue,保存该路径并更新optvalue.
题目
pata1003 Emergency
题意:给出N个城市,M条无向边。每个城市都有一定的救援小队,所有边的边权已知。求起点到终点的最短路径条数以及最短路径上的救援小组数目之和(多条最短路径取最大的)
num[i]表示:从起点s到达顶点i的最短路径条数,初始num[s]=1,其余num[i]为0。
w[i]表示:最短路径上,起点s到达顶点i的点权和。
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
const int maxn=510;
int n,m;
int G[maxn][maxn];
int d[maxn];
bool vis[maxn]={false};
int c1,c2;//起点,终点
int val[maxn];//记录点权
int num[maxn];//记录起点到i之间最短路径的条数
int w[maxn];//最短路径情况下,记录起点到i之间的最大权值
void dijkstra(int s)
{
fill(d,d+maxn,inf);
memset(num,0,sizeof num);
memset(w,0,sizeof w);
d[s]=0;
//初始化num和w
num[s]=1;
w[s]=val[s];
for(int i=0;i<n;i++)
{
int u=-1,MIN=inf;
for(int j=0;j<n;j++)
{
if(vis[j]==false && d[j]<MIN)
{
u=j;
MIN=d[j];
}
}
if(u==-1) return;
vis[u]=true;
for(int v=0;v<n;v++)
{
//以u为中继节点做松弛操作
if(vis[v]==false && G[u][v]!=inf )
{
if( d[u]+G[u][v]<d[v] )
{
d[v]=d[u]+G[u][v];
num[v]=num[u];
w[v]=w[u]+val[v];
}
else if( d[u]+G[u][v]==d[v] )
{
num[v]+=num[u];
if( w[u]+val[v] > w[v]) w[v]=w[u]+val[v];
}
}
}
}
}
int main()
{
cin>>n>>m>>c1>>c2;
for(int i=0;i<n;i++) cin>>val[i];
fill(G[0],G[0]+maxn*maxn,inf);
for(int i=0;i<m;i++)
{
int A,B,len;
scanf("%d %d %d",&A,&B,&len);
G[A][B]=min(len,G[A][B]);
G[B][A]=G[A][B];//无向图
}
dijkstra(c1);
cout<<num[c2]<<" "<<w[c2]<<endl;
return 0;
}
pata1087 All Roads Lead to Rome
注意dfs中的复制操作path=temppath
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
const int maxn=210;
int n,k;
int num=0;
map<string,int> city2int;
map<int,string> int2city;
string start;
int happiness[maxn];
int dst;
int G[maxn][maxn];
int dist[maxn];
bool vis[maxn];
vector<int> pre[maxn];
void dijkstra(int s)
{
memset(vis,false,sizeof vis);
fill(dist,dist+maxn,inf);
dist[s]=0;
for(int i=0;i<num;i++)
{
int u=-1,MIN=inf;
for(int j=0;j<num;j++)
{
if(!vis[j] && dist[j]<MIN)
{
u=j;
MIN=dist[j];
}
}
if(u==-1) return;
vis[u]=true;
for(int v=0;v<num;v++)
{
if(!vis[v] && G[u][v]!=inf)
{
if( dist[u]+G[u][v]<dist[v] )
{
dist[v]=dist[u]+G[u][v];
pre[v].clear();
pre[v].push_back(u);
}
else if( dist[u]+G[u][v]==dist[v] )
{
pre[v].push_back(u);
}
}
}
}
}
int happval=0;
double aver_happval=0.0;
vector<int> path,temppath;
int routenum=0;
//从hone dfs到target
void dfs(int u)
{
if(u==0)
{
routenum++;
temppath.push_back(u);
int val=0;
for(int i=0;i<temppath.size();i++) val+=happiness[temppath[i]];
if(val>happval)
{
happval=val;
aver_happval=happval/(temppath.size()-1);
path=temppath;
}
else if(val==happval)
{
double my_aver_happval=happval/(temppath.size()-1);
if(my_aver_happval>aver_happval)
{
aver_happval=my_aver_happval;
path=temppath;
}
}
temppath.pop_back();
return;
}
temppath.push_back(u);
for(int i=0;i<pre[u].size();i++)
{
dfs(pre[u][i]);
}
temppath.pop_back();
}
int main()
{
cin>>n>>k;
cin>>start;
city2int[start]=num;
int2city[num]=start;
num++;
for(int i=0;i<n-1;i++)
{
string s;
int t;
cin>>s>>t;
if(s=="ROM") dst=num;
city2int[s]=num;
int2city[num]=s;
happiness[num]=t;
num++;
}
fill(G[0],G[0]+maxn*maxn,inf);
for(int i=0;i<k;i++)
{
string a,b;
int t;
cin>>a>>b>>t;
int a1=city2int[a];
int b1=city2int[b];
G[a1][b1]=min(t,G[a1][b1]);
G[b1][a1]=G[a1][b1];
}
dijkstra(0);
dfs(dst);
cout<<routenum<<" "<<dist[dst]<<" "<<happval<<" "<<(int)aver_happval<<endl;
for(int i=path.size()-1;i>0;i--)
{
cout<<int2city[path[i]]<<"->";
}
cout<<"ROM"<<endl;
return 0;
}
Floyd算法
求全源最短路径,其边值可为负权,基本思想为 对(i,j)这条路径做n次松弛操作,松弛完后即是最短路径。O(n^3)
预处理:
fill(dis[0],dis[0]+maxn*maxn,inf);
cin>>n>>m;//边数,顶点数
for(int i=0;i<n;i++)
{
cin>>u>>v>>w;
dis[u][v]=w;
}
void Floyd()
{
for(int k=0;k<n;k++)
{
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
if(dis[i][k]!=inf && dis[k][j]!=inf && dis[i][k]+dis[k][j]<dis[i][j])
{
dis[i][j]=dis[i][k]+dis[k][j];
}
}
}
}
}
Bellman-Ford和SPFA算法
能够处理负边权,算法事件复杂度:O(VE)
对图中的边进行V-1轮操作,每轮都遍历图中所有的边u->v,以u为中介结点对v进行松弛。
判断负环:如果再进行一轮操作,仍然能够被松弛,说明存在负环。
优化:SPFA算法
时间复杂度:O(kE)k一般不超过2
思想:只有当某个顶点u的d[u]值改变,从它出发的v(边u->v)的d[v]值才会改变。
queue<int> Q;
源点s入队;
while(队列非空)
{
取出队首元素u;
for(u的所有邻接边u->v)
{
if(d[u]+dis<d[v])
{
d[v]=d[u]+dis;
if(v不在当前队列)
{
v入队;
if(v入队的次数大于n-1)
{
说明有负环;
return;
}
}
}
}
}
拓扑排序与关键路径
DAG:有向无环图
拓扑排序
C1是C5的前驱,C1是C3的直接前驱
一个AOV网的拓扑序列不是唯一的。
如何检测AOV网是否有环:如果拓扑序列包含了所有的顶点,说明没有环;否则有环。
关键路径
v2事件说明A活动结束,B,C活动开始
源点:入度为0的顶点
汇点:出度为0的顶点
关键路径:源点到汇点最长的路径