图论
以图为研究对象,图论中的图是由若干给定的点及连接两点的线所构成的图形,用来描述某些实体之间的某种特定关系,用点代表实体,用连接两点的线表示两个实体间具有的某种关系,细分有有向图、无向图,有环图、无环图,连接上可以有权值或无权值,重点在表示连接关系、处理的方法上
连接表示方法
1.邻接矩阵
2.邻接表
3.链式前向星
拓扑排序
P4017 最大食物链计数
#include<bits/stdc++.h>
using namespace std;
const int N=5005,MOD=80112002;
int n,m,dp[N],out[N],in[N];
bool reflect[N][N];
signed main()
{
cin>>n>>m;
int a,b;
for(int i=1;i<=m;i++)
{
cin>>a>>b;
in[b]++;
out[a]++;
reflect[a][b]=1;
}
int marki;
queue<int> q;
for(int i=1;i<=n;i++)
{
if(in[i]==0)
{
marki=i;//可能有多个
q.push(i);
dp[i]=1;
}
}
int ans=0;
while(!q.empty())//拓扑排序的思想
{
int t=q.front();
q.pop();
for(int k=1;k<=n;k++)
{
if(reflect[t][k])
{
dp[k]=(dp[k]+dp[t])%MOD;
in[k]--;
if(in[k]==0)
{
q.push(k);
if(out[k]==0)
{
ans=(ans+dp[k])%MOD;//这里可能有多个最底层
}
}
}
}
}
cout<<ans;
return 0;
}
P2712 摄像头
#include<bits/stdc++.h>
using namespace std;
int n,in[550],cnt;
bool matrix[550][550],film[550];
int main()
{
cin>>n;
int a,b,t,maxn=0;
for(int i=1;i<=n;i++)
{
cin>>a>>t;
film[a]=1;
for(int j=0;j<t;j++)
{
cin>>b;
matrix[a][b]=1;
in[b]++;
}
maxn=max(maxn,a);
}
queue<int> q;
for(int i=0;i<=maxn;i++)
{
if(in[i]==0&&film[i]==1)
{
q.push(i);
}
}
while(!q.empty())
{
int c=q.front();
q.pop();
cnt++;
for(int k=0;k<=maxn;k++)
{
if(matrix[c][k]&&film[k]==1)
{
in[k]--;
if(in[k]==0)
{
q.push(k);
}
}
}
}
if(cnt==n) cout<<"YES";
else cout<<n-cnt;
return 0;
}
P1122 最大子树和
#include<bits/stdc++.h>
using namespace std;//无向的
const int N=16010;
int n,dp[N],out[N],head[N*2],cnt,maxi=1;
bool used[N];
struct Edge
{
int u,v,next;
}edge[N*2];
void add(int u,int v)//链式前向星
{
edge[cnt].u=u;
edge[cnt].v=v;
edge[cnt].next=head[u];
head[u]=cnt++;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
head[i]=-1;
}
for(int i=1;i<=n;i++)
{
cin>>dp[i];
}
int b,c;
for(int i=1;i<n;i++)
{
cin>>b>>c;
add(b,c);
add(c,b);
out[b]++;
out[c]++;
}
for(int i=1;i<=n;i++)
{
if(out[i]>out[maxi]) maxi=i;
}
//cout<<maxi<<endl;
queue<int> q;
for(int i=1;i<=n;i++)
{
if(out[i]==1)
{
q.push(i);
used[i]=1;
}
}
while(!q.empty())
{
int t=q.front();
q.pop();
//cout<<" t的值是"<<t<<endl;
for(int i=head[t];i!=-1;i=edge[i].next)
{
//cout<<" i取了"<<i<<endl;
int num=edge[i].v;
//cout<<" numshi "<<num<<endl;
if(used[num]==0)
{
out[num]--;
dp[num]=max(dp[num],dp[num]+dp[t]);
//cout<<"dp[i]为 "<<dp[num]<<endl;
if(out[num]==0||(num!=maxi&&out[num]==1))
{
q.push(num);
used[num]=1;
}
}
}
}
int maxn=-2000000;
for(int i=1;i<=n;i++)
{
maxn=max(maxn,dp[i]);
}
cout<<maxn;
return 0;
}
最短路径算法
有Floyd算法,Bellman-Ford算法、SPFA算法和Dijkstra算法
图的规模小,用Floyd。若边的权值有负数,需要判断负圈;图的规模大,且边的权值非负,用Dijkstra;图的规模大,且边的权值有负数,用SPFA,需要判断负圈。Floyd、dijkstra是优选算法,分别解决多源对多源问题,单源对多源问题,反向建图可以达到多源对单源,bellman-ford可以用来判断负环
Floyd-Warshall
允许有负数,结点n<300,用邻接矩阵,重点在不一定是单源
P3371 【模板】单源最短路径(弱化版)
#include<bits/stdc++.h>
using namespace std;
#define int long
const int INF=2147483647;
int n,m,s;
int dp[310][310];
signed main()
{
cin>>n>>m>>s;
int a,b,c;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
dp[i][j]=INF;
}
}
for(int i=0;i<m;i++)
{
cin>>a>>b>>c;
dp[a][b]=min(dp[a][b],c);//注意初始化取最短边细节,可能会有重边
}
for(int k=1;k<=n;k++)
{
for(int i=1;i<=n;i++)
{
if(dp[i][k]!=INF)
{
for(int j=1;j<=n;j++)
{
if(dp[k][j]!=INF)
{
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]);
}
}
}
}
}
for(int i=1;i<=n;i++)
{
if(s==i) cout<<"0 ";
else cout<<dp[s][i]<<" ";
}
return 0;
}
判断负环可以用自己连结为0,再后来变成负数看出
Bellman-Ford
用来解决单源路径问题,n*m<10^7,使用邻接表
P3371 【模板】单源最短路径(弱化版)
#include<bits/stdc++.h>
using namespace std;
#define int long long
const long M=1e5+10,N=1e3+10,INF=2147483647;
int n,m,s;
struct Edge
{
int u,v,w;
}edge[M];
int d[N];//pre是连接的前驱节点,d是连接的距离
signed main()
{
cin>>n>>m>>s;
for(int i=0;i<m;i++)
{
cin>>edge[i].u>>edge[i].v>>edge[i].w;
}
for(int i=1;i<=n;i++)
{
d[i]=INF;
}
d[s]=0;
for(int i=1;i<=n;i++)
{
for(int k=0;k<m;k++)
{
int x=edge[k].u,y=edge[k].v;
d[y]=min(d[y],d[x]+edge[k].w);
//cout<<"x="<<x<<" d[x]="<<d[x]<<" y="<<y<<" d[y]="<<d[y]<<endl;
//cout<<d[y]+edge[k].w<<endl;
}
}
for(int i=1;i<=n;i++)
{
cout<<d[i]<<" ";
}
return 0;
}
SPFA
用邻接表和链式前向星都行,用于nm大于上述规则的题目,但是SPFA算法不稳定,最坏情况O(mn)
P3371 【模板】单源最短路径(弱化版)
#include<bits/stdc++.h>
using namespace std;
#define int long
int a=pow(2,31)-1;
const int N=1e5+10,M=2e5+10,INF=a;
struct Edge
{
int from,to,w;
Edge(int a,int b,int c)//内构函数
{
from=a;
to=b;
w=c;
}
};
vector<Edge> e[N];
int n,m,s;
signed main()
{
cin>>n>>m>>s;
for(int i=0;i<m;i++)
{
int a,b,c;
cin>>a>>b>>c;
e[a].push_back(Edge(a,b,c));//vector的用法,push_back
}
int d[N];
bool inq[N];
for(int i=1;i<=n;i++)
{
d[i]=INF;
inq[i]=false;
}
d[s]=0;
queue<int> q;
q.push(s);
inq[s]=true;
while(!q.empty())
{
int t=q.front();
q.pop();
inq[t]=false;
for(int i=0;i<e[t].size();i++)
{
int v=e[t][i].to,wei=e[t][i].w;//二维数组,存储了结构体变量
if(d[t]+wei<d[v])
{
d[v]=d[t]+wei;
if(!inq[v])
{
inq[v]=true;
q.push(v);
}
}
}
}
for(int i=1;i<=n;i++)
{
cout<<d[i]<<" ";
}
return 0;
}
Dijkstra
最高效的最短路径算法,使用了贪心的思想
#include<bits/stdc++.h>
using namespace std;
#define int long
long a=pow(2,31)-1;
const int N=1e5+10,M=2e5+10;
const long INF=a;
struct Edge
{
int from,to,w;
Edge(int a,int b,int c)//内构函数
{
from=a;
to=b;
w=c;
}
};
struct s_node
{
int id,dis;
s_node(int b,int c)//内构函数初始化方便
{
id=b;
dis=c;
}
bool operator<(const s_node &a) const//操作符重载
{
return dis>a.dis;
}
};
vector<Edge> e[N];
long n,m,s,d[N],done[N];
void dijkstra()
{
for(int i=1;i<=n;i++)
{
d[i]=INF;
}
d[s]=0;
priority_queue<s_node,vector<s_node>,less<s_node>> Q;
Q.push(s_node(s,d[s]));
while(!Q.empty())
{
s_node u=Q.top();
Q.pop();
if(done[u.id])
{
continue;
}
done[u.id]=true;
for(int i=0;i<e[u.id].size();i++)
{
Edge y=e[u.id][i];
if(done[y.to])
{
continue;
}
if(d[y.to]>y.w+u.dis)
{
d[y.to]=y.w+u.dis;
Q.push(s_node((y.to),d[y.to]));
}
}
}
for(int i=1;i<=n;i++)
{
cout<<d[i]<<" ";
}
}
signed main()
{
cin>>n>>m>>s;
for(int i=0;i<m;i++)
{
int a,b,c;
cin>>a>>b>>c;
e[a].push_back(Edge(a,b,c));//vector的用法,push_back
}
dijkstra();
return 0;
}
如果计算从各个节点到一个节点的情况,可以用反向图来做
最小生成树
用kruskal算法来做,同样的,最大生成树也是
分层图
#include<bits/stdc++.h>
using namespace std;
struct Edge
{
int to,next,cost;
}edge[2500001];
int cnt,head[110005];
void add_edge(int u,int v,int c=0)
{
edge[++cnt]=(Edge){v,head[u],c};
head[u]=cnt;
}
int dis[110005];
bool vis[110005];
void Dijkstra(int s)
{
memset(dis,0x3f,sizeof(dis));
dis[s]=0;
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > points;
points.push(make_pair(0,s));
while(!points.empty())
{
int u=points.top().second;
points.pop();
if(!vis[u])
{
vis[u]=1;
for(int i=head[u];i;i=edge[i].next)
{
int to=edge[i].to;
if(dis[to]>dis[u]+edge[i].cost)
{
dis[to]=dis[u]+edge[i].cost;
points.push(make_pair(dis[to],to));
}
}
}
}
}
int main()
{
int n,m,k,s,t;
cin>>n>>m>>k>>s>>t;
int u,v,c;
for(int i=0;i<m;++i)
{
cin>>u>>v>>c;
add_edge(u,v,c);
add_edge(v,u,c);
for(int j=1;j<=k;++j)
{
add_edge(u+(j-1)*n,v+j*n);
add_edge(v+(j-1)*n,u+j*n);
add_edge(u+j*n,v+j*n,c);
add_edge(v+j*n,u+j*n,c);//连接分层图
}
}
for(int i=1;i<=k;++i)
{
add_edge(t+(i-1)*n,t+i*n);
}
Dijkstra(s);
cout<<dis[t+k*n];
return 0;
}
各种图
完全图
也称简单完全图。假设一个图有n个顶点,那么如果任意两个顶点之间都有边的话,该图就称为完全图
连通图(无向图)、强连通图(有向图)
从顶点v到w有路径,就称顶点v和m连通。(路径是由顶点和相邻顶点序偶构成的边所形成的序列,其实就是一堆相连的顶点及其边)
如果图中任意俩顶点都连通,则该图为连通图
若从顶点v到m有路径,则称这俩顶点时强连通的。若任意一对顶点都是强连通的,称此图为强连通图
连通分量(无向图)、强连通分量(有向图)
与连通图对应,极大连通子图是无向图的连通分量,极大强连通子图是有向图的强连通分量
极大连通分量(无向图)极大强连通分量(有向图)
极大是要求该连通子图/强连通子图包含其所有的边
极小连通分量(无向图)极小强联通分量(有向图)
极小是在保持连通的情况下使边数最少的子图(暗指无向图),有向图中的极大强连通子图称为有向图的强连通分量
生成树
连通图的生成树是包含图中全部顶点的一个极小连通子图
生成森林
在非连通图中,连通分量的生成树构成了非连通图的生成森林
tarjan算法博主正在更新中